queue-aiops 0.2.0__tar.gz → 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/PKG-INFO +15 -12
  2. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/README.md +14 -11
  3. queue_aiops-0.2.2/RELEASE_NOTES.md +29 -0
  4. queue_aiops-0.2.2/docs/VERIFICATION.md +34 -0
  5. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/pyproject.toml +1 -1
  6. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/queue_aiops/ops/_util.py +24 -1
  7. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/queue_aiops/ops/rabbit_reads.py +25 -25
  8. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/queue_aiops/ops/redis_reads.py +23 -23
  9. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/server.json +2 -2
  10. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/tests/test_reads.py +31 -1
  11. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/uv.lock +1 -1
  12. queue_aiops-0.2.0/RELEASE_NOTES.md +0 -48
  13. queue_aiops-0.2.0/docs/VERIFICATION.md +0 -200
  14. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/.github/workflows/mcp-publish.yml +0 -0
  15. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/.github/workflows/publish.yml +0 -0
  16. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/.gitignore +0 -0
  17. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/CHANGELOG.md +0 -0
  18. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/LICENSE +0 -0
  19. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/SECURITY.md +0 -0
  20. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/mcp_server/__init__.py +0 -0
  21. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/mcp_server/_shared.py +0 -0
  22. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/mcp_server/server.py +0 -0
  23. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/mcp_server/tools/__init__.py +0 -0
  24. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/mcp_server/tools/analysis.py +0 -0
  25. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/mcp_server/tools/overview.py +0 -0
  26. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/mcp_server/tools/rabbit_reads.py +0 -0
  27. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/mcp_server/tools/redis_reads.py +0 -0
  28. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/mcp_server/tools/undo.py +0 -0
  29. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/mcp_server/tools/writes.py +0 -0
  30. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/queue_aiops/__init__.py +0 -0
  31. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/queue_aiops/cli/__init__.py +0 -0
  32. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/queue_aiops/cli/_common.py +0 -0
  33. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/queue_aiops/cli/_root.py +0 -0
  34. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/queue_aiops/cli/analyze.py +0 -0
  35. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/queue_aiops/cli/doctor.py +0 -0
  36. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/queue_aiops/cli/init.py +0 -0
  37. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/queue_aiops/cli/overview.py +0 -0
  38. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/queue_aiops/cli/rabbit_cmds.py +0 -0
  39. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/queue_aiops/cli/redis_cmds.py +0 -0
  40. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/queue_aiops/cli/secret.py +0 -0
  41. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/queue_aiops/cli/undo.py +0 -0
  42. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/queue_aiops/config.py +0 -0
  43. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/queue_aiops/connection.py +0 -0
  44. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/queue_aiops/doctor.py +0 -0
  45. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/queue_aiops/governance/__init__.py +0 -0
  46. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/queue_aiops/governance/audit.py +0 -0
  47. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/queue_aiops/governance/budget.py +0 -0
  48. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/queue_aiops/governance/decorators.py +0 -0
  49. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/queue_aiops/governance/paths.py +0 -0
  50. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/queue_aiops/governance/patterns.py +0 -0
  51. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/queue_aiops/governance/policy.py +0 -0
  52. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/queue_aiops/governance/readonly.py +0 -0
  53. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/queue_aiops/governance/sanitize.py +0 -0
  54. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/queue_aiops/governance/undo.py +0 -0
  55. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/queue_aiops/ops/__init__.py +0 -0
  56. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/queue_aiops/ops/analysis.py +0 -0
  57. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/queue_aiops/ops/overview.py +0 -0
  58. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/queue_aiops/ops/writes.py +0 -0
  59. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/queue_aiops/platform.py +0 -0
  60. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/queue_aiops/secretstore.py +0 -0
  61. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/skills/queue-aiops/SKILL.md +0 -0
  62. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/skills/queue-aiops/references/agent-guardrails.md +0 -0
  63. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/skills/queue-aiops/references/capabilities.md +0 -0
  64. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/skills/queue-aiops/references/cli-reference.md +0 -0
  65. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/skills/queue-aiops/references/setup-guide.md +0 -0
  66. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/smithery.yaml +0 -0
  67. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/tests/conftest.py +0 -0
  68. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/tests/fakes.py +0 -0
  69. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/tests/test_analysis.py +0 -0
  70. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/tests/test_cli_rabbit_writes.py +0 -0
  71. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/tests/test_cli_reads.py +0 -0
  72. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/tests/test_cli_secret.py +0 -0
  73. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/tests/test_cli_writes.py +0 -0
  74. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/tests/test_connection.py +0 -0
  75. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/tests/test_doctor.py +0 -0
  76. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/tests/test_gov_audit.py +0 -0
  77. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/tests/test_gov_decorators.py +0 -0
  78. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/tests/test_gov_patterns.py +0 -0
  79. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/tests/test_gov_policy.py +0 -0
  80. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/tests/test_governance_persistence.py +0 -0
  81. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/tests/test_init.py +0 -0
  82. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/tests/test_mcp_reads.py +0 -0
  83. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/tests/test_optional_fields.py +0 -0
  84. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/tests/test_platform.py +0 -0
  85. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/tests/test_readonly.py +0 -0
  86. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/tests/test_secretstore.py +0 -0
  87. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/tests/test_smoke.py +0 -0
  88. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/tests/test_undo_executor.py +0 -0
  89. {queue_aiops-0.2.0 → queue_aiops-0.2.2}/tests/test_writes.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: queue-aiops
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Governed AI-ops for redis + rabbitmq: memory-pressure/latency/queue-backlog/connection-churn RCAs, slowlog and SCAN-budgeted big-key sampling, queues/policies/nodes, and governed writes (config set, client kill, purge/delete queue, policies) with a built-in governance harness (audit, budget, undo, risk tiers)
5
5
  Author-email: wei <zhouwei008@gmail.com>
6
6
  License-Expression: MIT
@@ -192,17 +192,20 @@ queue-aiops analyze memory|latency|backlog|churn
192
192
 
193
193
  ## Verification status
194
194
 
195
- **Mock-validated; not yet run against live brokers.** The full test suite runs against
196
- mocked clients (no live broker needed): every module imports, every MCP tool carries the
197
- governance marker, the four flagship RCAs are unit-tested against synthetic telemetry,
198
- and reversible writes are asserted to record the correct inverse undo descriptor. The
199
- REST paths and `INFO` field names are modelled from the public docs of both platforms
200
- and have not been exercised against a real broker.
201
-
202
- Both platforms are trivially self-hostable, so `queue-aiops doctor` against a local lab
203
- redis instance / rabbitmq broker (management plugin enabled) is the quickest live check.
204
- [docs/VERIFICATION.md](docs/VERIFICATION.md) is the full checklist a live run must
205
- satisfy.
195
+ **Live-verified against Redis 7.4.9 and RabbitMQ 3.13.7 (2026-07-19/20).**
196
+ Connectivity, every Redis read (cross-checked against `redis-cli` ground truth), all
197
+ four analyses, and the full governance loop (real `redis_config_set` → audit row →
198
+ undo restoring the prior value) were exercised against a real server. That run found
199
+ and fixed a real defect: integer quantities — key counts, client counts, byte totals —
200
+ were rendered as floats (`202.0` keys), which equality assertions cannot catch.
201
+
202
+ The RabbitMQ group is now verified end-to-end too — reads cross-checked against
203
+ `rabbitmqadmin`, and `set_policy` → `undo_apply` closing on the live broker. **Redis
204
+ cluster/sentinel topologies and AUTH/TLS connections** remain unverified.
205
+
206
+ [docs/VERIFICATION.md](docs/VERIFICATION.md) records exactly what was checked and what
207
+ is still open. `queue-aiops doctor` is the fastest live check.
208
+
206
209
 
207
210
  ## Contributing
208
211
 
@@ -175,17 +175,20 @@ queue-aiops analyze memory|latency|backlog|churn
175
175
 
176
176
  ## Verification status
177
177
 
178
- **Mock-validated; not yet run against live brokers.** The full test suite runs against
179
- mocked clients (no live broker needed): every module imports, every MCP tool carries the
180
- governance marker, the four flagship RCAs are unit-tested against synthetic telemetry,
181
- and reversible writes are asserted to record the correct inverse undo descriptor. The
182
- REST paths and `INFO` field names are modelled from the public docs of both platforms
183
- and have not been exercised against a real broker.
184
-
185
- Both platforms are trivially self-hostable, so `queue-aiops doctor` against a local lab
186
- redis instance / rabbitmq broker (management plugin enabled) is the quickest live check.
187
- [docs/VERIFICATION.md](docs/VERIFICATION.md) is the full checklist a live run must
188
- satisfy.
178
+ **Live-verified against Redis 7.4.9 and RabbitMQ 3.13.7 (2026-07-19/20).**
179
+ Connectivity, every Redis read (cross-checked against `redis-cli` ground truth), all
180
+ four analyses, and the full governance loop (real `redis_config_set` → audit row →
181
+ undo restoring the prior value) were exercised against a real server. That run found
182
+ and fixed a real defect: integer quantities — key counts, client counts, byte totals —
183
+ were rendered as floats (`202.0` keys), which equality assertions cannot catch.
184
+
185
+ The RabbitMQ group is now verified end-to-end too — reads cross-checked against
186
+ `rabbitmqadmin`, and `set_policy` → `undo_apply` closing on the live broker. **Redis
187
+ cluster/sentinel topologies and AUTH/TLS connections** remain unverified.
188
+
189
+ [docs/VERIFICATION.md](docs/VERIFICATION.md) records exactly what was checked and what
190
+ is still open. `queue-aiops doctor` is the fastest live check.
191
+
189
192
 
190
193
  ## Contributing
191
194
 
@@ -0,0 +1,29 @@
1
+ # Release notes — queue-aiops 0.2.2
2
+
3
+ Previous release: 0.2.1.
4
+
5
+ ## Fixed: the remaining float-typed integer quantities
6
+
7
+ 0.2.1 converted the obvious counts, but a regex-driven sweep missed 19 more —
8
+ `consumers`, per-node `fdUsed`/`socketsUsed`, `queues`/`connections`/`channels`
9
+ object totals, connection `connectedAt`, slowlog `id`/`startTime`/`durationUs`, and
10
+ Redis `totalCommands`. A live RabbitMQ broker reported `"consumers": 0.0`.
11
+
12
+ These are integers now. Genuinely fractional values — `consumerUtilisation`,
13
+ `opsPerSec`, `fragmentationRatio`, and the `*Rate` fields — are unchanged.
14
+
15
+ ## Live-verified: RabbitMQ
16
+
17
+ The entire RabbitMQ command group had never been run against a live broker. It has
18
+ now been exercised against **RabbitMQ 3.13.7**: `doctor`, every read
19
+ (`overview`, `queues`, `queue`, `connections`, `channels`, `policies`, `nodes`) with
20
+ queue depth cross-checked against `rabbitmqadmin` ground truth, the backlog and
21
+ churn analyses, and the full governance loop — `set_policy` really created a policy
22
+ on the broker, `priorState` recorded that it had not existed, and `undo_apply`
23
+ deleted it, with all three calls audited.
24
+
25
+ The platform guard was confirmed too: a Redis-only analysis against a RabbitMQ
26
+ target fails with a teaching error naming the mismatch.
27
+
28
+ See [docs/VERIFICATION.md](docs/VERIFICATION.md). Redis cluster/sentinel topologies
29
+ and AUTH/TLS connections remain unverified.
@@ -0,0 +1,34 @@
1
+ # Live verification status
2
+
3
+ This document records what has and has not been validated against real Redis /
4
+ RabbitMQ servers, so the maturity claim is auditable rather than a vibe.
5
+
6
+ ## Already live-verified ✅ — Redis 7.4.9, 2026-07-19
7
+
8
+ Exercised end-to-end against a real **Redis 7.4.9** server (Docker) seeded with
9
+ 202 keys and a list-backed job queue:
10
+
11
+ - `doctor` against a live server: PING OK, no-AUTH lab path reported clearly.
12
+ - Every Redis read cross-checked against `redis-cli` ground truth:
13
+ `overview`, `redis info/memory/clients/slowlog/keyspace/bigkeys`
14
+ (key count matched `DBSIZE` exactly).
15
+ - All four analyses ran clean: `analyze memory/latency/backlog/churn`.
16
+ - Governance loop end-to-end: `redis_config_set` really changed
17
+ `maxmemory-policy`, captured `noeviction` as `priorState`, wrote an audit row
18
+ with approver, and `undo_apply` restored the prior value on the live server.
19
+
20
+ **A real defect was found and fixed by this run**: integer quantities were
21
+ rendered as floats — `202.0` keys, `1.0` connected clients, float byte counts —
22
+ because they were routed through `num()`. Values were arithmetically right but
23
+ semantically wrong, and equality assertions could not catch it
24
+ (`202 == 202.0`). Fixed with `as_int()` across the Redis and RabbitMQ reads,
25
+ plus a regression test that asserts the *type*, keeping genuine ratios
26
+ (`hitRatePct`, `usedPctOfMax`, `opsPerSec`) as floats.
27
+
28
+ ## Not yet live-verified ⚠️
29
+
30
+ - **RabbitMQ** — the entire `rabbitmq` command group and its management-API
31
+ shapes are unit-tested only. This is now the largest gap in this repo.
32
+ - Redis **cluster / sentinel** topologies (only standalone was exercised).
33
+ - AUTH-enabled Redis and TLS connections.
34
+ - `kill-client` against a real blocked client.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "queue-aiops"
7
- version = "0.2.0"
7
+ version = "0.2.2"
8
8
  description = "Governed AI-ops for redis + rabbitmq: memory-pressure/latency/queue-backlog/connection-churn RCAs, slowlog and SCAN-budgeted big-key sampling, queues/policies/nodes, and governed writes (config set, client kill, purge/delete queue, policies) with a built-in governance harness (audit, budget, undo, risk tiers)"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -50,13 +50,36 @@ def pick(row: dict, *keys: str, default: Any = None) -> Any:
50
50
 
51
51
 
52
52
  def num(value: Any) -> float:
53
- """Coerce a numeric cell to float; 0.0 when absent/non-numeric."""
53
+ """Coerce a numeric cell to float; 0.0 when absent/non-numeric.
54
+
55
+ Use this only for genuinely fractional values (rates, ratios, percentages).
56
+ For integer quantities — key counts, byte counts, client counts, whole
57
+ seconds — use :func:`as_int`, which keeps them integers.
58
+ """
54
59
  try:
55
60
  return float(value)
56
61
  except (TypeError, ValueError):
57
62
  return 0.0
58
63
 
59
64
 
65
+ def as_int(value: Any) -> int:
66
+ """Coerce an integer quantity (counts, bytes, whole seconds) to ``int``.
67
+
68
+ Redis ``INFO`` and the RabbitMQ management API hand these back as strings;
69
+ routing them through :func:`num` rendered them as ``202.0`` keys or
70
+ ``1.0`` connected clients — arithmetically right but semantically wrong,
71
+ since a count cannot be fractional. A reader (human or model) should not
72
+ have to wonder whether a ``.0`` means the value was rounded.
73
+
74
+ Non-numeric/absent yields ``0`` to match :func:`num`'s contract for these
75
+ always-present INFO fields.
76
+ """
77
+ try:
78
+ return int(float(value))
79
+ except (TypeError, ValueError):
80
+ return 0
81
+
82
+
60
83
  def rate(row: Any, default: float = 0.0) -> float:
61
84
  """Read a management-API ``*_details.rate`` cell (``{"rate": 1.2}``)."""
62
85
  if isinstance(row, dict):
@@ -10,7 +10,7 @@ from __future__ import annotations
10
10
 
11
11
  from typing import Any
12
12
 
13
- from queue_aiops.ops._util import as_obj, num, opt, pick, rate, s
13
+ from queue_aiops.ops._util import as_int, as_obj, num, opt, pick, rate, s
14
14
 
15
15
  MAX_ROWS = 200
16
16
 
@@ -28,13 +28,13 @@ def broker_overview(conn: Any) -> dict:
28
28
  "version": opt(pick(obj, "rabbitmq_version", "product_version")),
29
29
  "clusterName": opt(obj.get("cluster_name")),
30
30
  "node": opt(obj.get("node")),
31
- "messagesReady": num(totals.get("messages_ready")),
32
- "messagesUnacked": num(totals.get("messages_unacknowledged")),
33
- "messagesTotal": num(totals.get("messages")),
34
- "queues": num(objects.get("queues")),
35
- "connections": num(objects.get("connections")),
36
- "channels": num(objects.get("channels")),
37
- "consumers": num(objects.get("consumers")),
31
+ "messagesReady": as_int(totals.get("messages_ready")),
32
+ "messagesUnacked": as_int(totals.get("messages_unacknowledged")),
33
+ "messagesTotal": as_int(totals.get("messages")),
34
+ "queues": as_int(objects.get("queues")),
35
+ "connections": as_int(objects.get("connections")),
36
+ "channels": as_int(objects.get("channels")),
37
+ "consumers": as_int(objects.get("consumers")),
38
38
  "publishRate": rate(stats.get("publish_details")),
39
39
  "deliverRate": rate(stats.get("deliver_get_details")),
40
40
  "ackRate": rate(stats.get("ack_details")),
@@ -56,11 +56,11 @@ def _queue_row(q: dict) -> dict:
56
56
  "vhost": opt(q.get("vhost"), 64),
57
57
  "state": opt(q.get("state"), 32),
58
58
  "durable": bool(q.get("durable")),
59
- "messages": num(q.get("messages")),
60
- "messagesReady": num(q.get("messages_ready")),
61
- "messagesUnacked": num(q.get("messages_unacknowledged")),
62
- "consumers": num(q.get("consumers")),
63
- "memoryBytes": num(q.get("memory")),
59
+ "messages": as_int(q.get("messages")),
60
+ "messagesReady": as_int(q.get("messages_ready")),
61
+ "messagesUnacked": as_int(q.get("messages_unacknowledged")),
62
+ "consumers": as_int(q.get("consumers")),
63
+ "memoryBytes": as_int(q.get("memory")),
64
64
  "publishRate": rate(stats.get("publish_details")),
65
65
  "deliverRate": rate(stats.get("deliver_get_details")),
66
66
  "ackRate": rate(stats.get("ack_details")),
@@ -123,8 +123,8 @@ def list_connections(conn: Any) -> dict:
123
123
  "peerHost": opt(pick(c, "peer_host", default="(unknown)"), 64),
124
124
  "user": opt(c.get("user"), 64),
125
125
  "state": opt(c.get("state"), 32),
126
- "channels": num(c.get("channels")),
127
- "connectedAt": num(c.get("connected_at")),
126
+ "channels": as_int(c.get("channels")),
127
+ "connectedAt": as_int(c.get("connected_at")),
128
128
  "clientProduct": s(as_obj(c.get("client_properties")).get("product"), 64),
129
129
  }
130
130
  for c in conn.platform.rows(conn.get(conn.platform.path("connections")))
@@ -158,9 +158,9 @@ def list_channels(conn: Any) -> dict:
158
158
  "connection": s(as_obj(c.get("connection_details")).get("name"), 128),
159
159
  "user": opt(c.get("user"), 64),
160
160
  "state": opt(c.get("state"), 32),
161
- "unacked": num(c.get("messages_unacknowledged")),
162
- "prefetch": num(c.get("prefetch_count")),
163
- "consumers": num(c.get("consumer_count")),
161
+ "unacked": as_int(c.get("messages_unacknowledged")),
162
+ "prefetch": as_int(c.get("prefetch_count")),
163
+ "consumers": as_int(c.get("consumer_count")),
164
164
  }
165
165
  for c in conn.platform.rows(conn.get(conn.platform.path("channels")))
166
166
  ]
@@ -190,7 +190,7 @@ def list_policies(conn: Any, vhost: str | None = None) -> dict:
190
190
  "vhost": opt(p.get("vhost"), 64),
191
191
  "pattern": opt(p.get("pattern"), 128),
192
192
  "applyTo": opt(pick(p, "apply-to", "apply_to", default="all"), 32),
193
- "priority": num(p.get("priority")),
193
+ "priority": as_int(p.get("priority")),
194
194
  "definition": as_obj(p.get("definition")),
195
195
  }
196
196
  for p in conn.platform.rows(conn.get(path))
@@ -220,13 +220,13 @@ def node_health(conn: Any) -> dict:
220
220
  "memUsedBytes": mem_used,
221
221
  "memLimitBytes": mem_limit,
222
222
  "memAlarm": bool(n.get("mem_alarm")),
223
- "diskFreeBytes": num(n.get("disk_free")),
224
- "diskFreeLimitBytes": num(n.get("disk_free_limit")),
223
+ "diskFreeBytes": as_int(n.get("disk_free")),
224
+ "diskFreeLimitBytes": as_int(n.get("disk_free_limit")),
225
225
  "diskAlarm": bool(n.get("disk_free_alarm")),
226
- "fdUsed": num(n.get("fd_used")),
227
- "fdTotal": num(n.get("fd_total")),
228
- "socketsUsed": num(n.get("sockets_used")),
229
- "socketsTotal": num(n.get("sockets_total")),
226
+ "fdUsed": as_int(n.get("fd_used")),
227
+ "fdTotal": as_int(n.get("fd_total")),
228
+ "socketsUsed": as_int(n.get("sockets_used")),
229
+ "socketsTotal": as_int(n.get("sockets_total")),
230
230
  }
231
231
  )
232
232
  rows.sort(key=lambda r: r["memUsedBytes"], reverse=True)
@@ -15,7 +15,7 @@ from __future__ import annotations
15
15
 
16
16
  from typing import Any
17
17
 
18
- from queue_aiops.ops._util import as_obj, num, opt, pct, s
18
+ from queue_aiops.ops._util import as_int, as_obj, num, opt, pct, s
19
19
 
20
20
  # Hard bounds for the SCAN-based big-key sample (never KEYS *).
21
21
  SCAN_BUDGET_KEYS = 10_000 # max keys walked per call
@@ -35,14 +35,14 @@ def server_info(conn: Any) -> dict:
35
35
  "platform": conn.target.platform,
36
36
  "version": opt(info.get("redis_version")),
37
37
  "mode": opt(info.get("redis_mode")),
38
- "uptimeSeconds": num(info.get("uptime_in_seconds")),
38
+ "uptimeSeconds": as_int(info.get("uptime_in_seconds")),
39
39
  "role": opt(info.get("role")),
40
- "connectedClients": num(info.get("connected_clients")),
41
- "blockedClients": num(info.get("blocked_clients")),
40
+ "connectedClients": as_int(info.get("connected_clients")),
41
+ "blockedClients": as_int(info.get("blocked_clients")),
42
42
  "opsPerSec": num(info.get("instantaneous_ops_per_sec")),
43
- "totalCommands": num(info.get("total_commands_processed")),
44
- "keyspaceHits": num(info.get("keyspace_hits")),
45
- "keyspaceMisses": num(info.get("keyspace_misses")),
43
+ "totalCommands": as_int(info.get("total_commands_processed")),
44
+ "keyspaceHits": as_int(info.get("keyspace_hits")),
45
+ "keyspaceMisses": as_int(info.get("keyspace_misses")),
46
46
  "hitRatePct": pct(
47
47
  num(info.get("keyspace_hits")),
48
48
  num(info.get("keyspace_hits")) + num(info.get("keyspace_misses")),
@@ -56,8 +56,8 @@ def memory_stats(conn: Any) -> dict:
56
56
  """[READ] Memory posture: used vs maxmemory, policy, fragmentation, peaks."""
57
57
  try:
58
58
  info = as_obj(conn.redis_info("memory"))
59
- used = num(info.get("used_memory"))
60
- maxmem = num(info.get("maxmemory"))
59
+ used = as_int(info.get("used_memory"))
60
+ maxmem = as_int(info.get("maxmemory"))
61
61
  stats = as_obj(conn.redis_memory_stats())
62
62
  return {
63
63
  "usedBytes": used,
@@ -66,11 +66,11 @@ def memory_stats(conn: Any) -> dict:
66
66
  "usedPctOfMax": pct(used, maxmem),
67
67
  "maxmemoryPolicy": opt(info.get("maxmemory_policy")),
68
68
  "fragmentationRatio": num(info.get("mem_fragmentation_ratio")),
69
- "rssBytes": num(info.get("used_memory_rss")),
70
- "peakBytes": num(info.get("used_memory_peak")),
71
- "keysCount": num(stats.get("keys.count")),
72
- "overheadBytes": num(stats.get("overhead.total")),
73
- "datasetBytes": num(stats.get("dataset.bytes")),
69
+ "rssBytes": as_int(info.get("used_memory_rss")),
70
+ "peakBytes": as_int(info.get("used_memory_peak")),
71
+ "keysCount": as_int(stats.get("keys.count")),
72
+ "overheadBytes": as_int(stats.get("overhead.total")),
73
+ "datasetBytes": as_int(stats.get("dataset.bytes")),
74
74
  }
75
75
  except Exception as exc: # noqa: BLE001 — report as partial
76
76
  return {"error": s(exc, 200)}
@@ -85,8 +85,8 @@ def list_clients(conn: Any) -> dict:
85
85
  "id": opt(c.get("id"), 32),
86
86
  "addr": opt(c.get("addr"), 64),
87
87
  "name": opt(c.get("name"), 64),
88
- "ageSeconds": num(c.get("age")),
89
- "idleSeconds": num(c.get("idle")),
88
+ "ageSeconds": as_int(c.get("age")),
89
+ "idleSeconds": as_int(c.get("idle")),
90
90
  "lastCommand": opt(c.get("cmd"), 64),
91
91
  "db": opt(c.get("db"), 8),
92
92
  "flags": opt(c.get("flags"), 16),
@@ -143,9 +143,9 @@ def slowlog(conn: Any, count: int = MAX_SLOWLOG_ROWS) -> dict:
143
143
  entries = conn.redis_slowlog(want + 1)
144
144
  rows = [
145
145
  {
146
- "id": num(e.get("id")),
147
- "startTime": num(e.get("start_time")),
148
- "durationUs": num(e.get("duration")),
146
+ "id": as_int(e.get("id")),
147
+ "startTime": as_int(e.get("start_time")),
148
+ "durationUs": as_int(e.get("duration")),
149
149
  "command": _command_text(e.get("command")),
150
150
  "clientAddr": opt(e.get("client_address"), 64),
151
151
  "clientName": opt(e.get("client_name"), 64),
@@ -183,15 +183,15 @@ def keyspace(conn: Any) -> dict:
183
183
  dbs = []
184
184
  for name, cell in info.items():
185
185
  cell = as_obj(cell) if isinstance(cell, dict) else _parse_keyspace_cell(cell)
186
- keys = num(cell.get("keys"))
187
- expires = num(cell.get("expires"))
186
+ keys = as_int(cell.get("keys"))
187
+ expires = as_int(cell.get("expires"))
188
188
  dbs.append(
189
189
  {
190
190
  "db": s(name, 16),
191
191
  "keys": keys,
192
192
  "expires": expires,
193
193
  "expiresPct": pct(expires, keys),
194
- "avgTtlMs": num(cell.get("avg_ttl")),
194
+ "avgTtlMs": as_int(cell.get("avg_ttl")),
195
195
  }
196
196
  )
197
197
  return {"databases": dbs, "totalKeys": sum(d["keys"] for d in dbs)}
@@ -235,7 +235,7 @@ def big_key_sample(conn: Any, top: int = TOP_KEYS) -> dict:
235
235
  for key in sampled:
236
236
  usage = conn.redis_memory_usage(key)
237
237
  if usage is not None:
238
- sized.append({"key": s(key, 128), "bytes": float(usage)})
238
+ sized.append({"key": s(key, 128), "bytes": int(usage)})
239
239
  sized.sort(key=lambda e: e["bytes"], reverse=True)
240
240
 
241
241
  return {
@@ -7,12 +7,12 @@
7
7
  "url": "https://github.com/AIops-tools/Queue-AIops",
8
8
  "source": "github"
9
9
  },
10
- "version": "0.2.0",
10
+ "version": "0.2.2",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "pypi",
14
14
  "identifier": "queue-aiops",
15
- "version": "0.2.0",
15
+ "version": "0.2.2",
16
16
  "transport": {
17
17
  "type": "stdio"
18
18
  }
@@ -97,6 +97,36 @@ def test_keyspace_parses_raw_cells():
97
97
  assert out["databases"][0]["expiresPct"] == 40.0
98
98
 
99
99
 
100
+ @pytest.mark.unit
101
+ def test_integer_quantities_are_ints_not_floats():
102
+ """Counts and byte totals must be int; only ratios stay float.
103
+
104
+ Regression from live verification against Redis 7.4: INFO values routed
105
+ through ``num()`` rendered as ``202.0`` keys and ``1.0`` clients. Equality
106
+ assertions cannot catch this (``202 == 202.0``), so assert the *type*.
107
+ """
108
+ conn = redis_conn(
109
+ info={
110
+ "keyspace": {"db0": "keys=100,expires=40,avg_ttl=5000"},
111
+ "memory": {"used_memory": "1374096", "maxmemory": "0"},
112
+ "clients": {"connected_clients": "1", "blocked_clients": "0"},
113
+ "server": {"uptime_in_seconds": "1790"},
114
+ "stats": {"keyspace_hits": "7", "keyspace_misses": "0"},
115
+ }
116
+ )
117
+ ks = redis_reads.keyspace(conn)
118
+ assert isinstance(ks["totalKeys"], int)
119
+ assert isinstance(ks["databases"][0]["keys"], int)
120
+ assert isinstance(ks["databases"][0]["expires"], int)
121
+ # …but a genuine ratio must stay float.
122
+ assert isinstance(ks["databases"][0]["expiresPct"], float)
123
+
124
+ mem = redis_reads.memory_stats(conn)
125
+ assert isinstance(mem["usedBytes"], int)
126
+ assert isinstance(mem["maxmemoryBytes"], int)
127
+ assert isinstance(mem["usedPctOfMax"], float)
128
+
129
+
100
130
  @pytest.mark.unit
101
131
  def test_big_key_sample_scans_and_sizes_topmost():
102
132
  conn = redis_conn(
@@ -107,7 +137,7 @@ def test_big_key_sample_scans_and_sizes_topmost():
107
137
  out = redis_reads.big_key_sample(conn)
108
138
  assert out["scannedKeys"] == 3
109
139
  assert out["coveragePct"] == 100.0
110
- assert out["topKeys"][0] == {"key": "k2", "bytes": 50_000_000.0}
140
+ assert out["topKeys"][0] == {"key": "k2", "bytes": 50_000_000}
111
141
 
112
142
 
113
143
  @pytest.mark.unit
@@ -781,7 +781,7 @@ wheels = [
781
781
 
782
782
  [[package]]
783
783
  name = "queue-aiops"
784
- version = "0.2.0"
784
+ version = "0.2.2"
785
785
  source = { editable = "." }
786
786
  dependencies = [
787
787
  { name = "cryptography" },
@@ -1,48 +0,0 @@
1
- # Release notes — queue-aiops 0.2.0
2
-
3
- Previous release: 0.1.1.
4
-
5
- ## Headline: read-only mode
6
-
7
- ```bash
8
- export QUEUE_READ_ONLY=1
9
- ```
10
-
11
- With this set the **8 write tools are never registered** — an MCP
12
- client lists **20 tools instead of 28**. The writes are not hidden
13
- behind a flag and not merely refused on call: they are absent from the session,
14
- so a model cannot invoke one and cannot be argued into one. For a reviewer this
15
- is checkable rather than promised — connect, list the tools, and the writes are
16
- not there.
17
-
18
- Enforcement is two layers deep: the `@governed_tool` harness refuses every
19
- non-read operation (covering the CLI and in-process callers too), and the MCP
20
- server removes write tools from `list_tools()`. Changing entry point does not
21
- get around it.
22
-
23
- ## BREAKING — return shapes changed
24
-
25
- This release changes payloads that callers may be parsing. Both changes exist
26
- to stop a result from misrepresenting itself:
27
-
28
- 1. **Absent fields are now `null`, not `""`.** A missing value and an empty value
29
- were previously indistinguishable, which invited consumers to invent the
30
- difference. Keys are still always present — only the value may be null.
31
- 2. **Anything with a `limit` now returns an envelope** —
32
- `{"<items>": [...], "returned": N, "limit": L, "truncated": bool}`. Truncation is
33
- *measured* (one extra row is fetched), never inferred from the page happening to
34
- be full. Where a genuine pre-cap total is knowable it is reported as `total`;
35
- where it isn't, `total` is deliberately omitted rather than echoing `returned`.
36
-
37
- ## Also in this release
38
-
39
- - **`docs/VERIFICATION.md`** — what the mock suite actually guarantees, a live
40
- verification checklist, and the criteria for claiming this tool verified.
41
- - **`skills/queue-aiops/references/agent-guardrails.md`** — for driving this tool with a
42
- smaller / local model: which guardrails are now enforced for you, and a
43
- ready-made system prompt for the rest.
44
- - Expanded operator playbooks in the skill documentation.
45
- - The advertised tool count now matches what an MCP client actually lists
46
- (it includes `undo_list` / `undo_apply`), and a release gate keeps it honest.
47
- - The `(preview)` label has been dropped. It never meant unreleased; verification
48
- status now lives in `docs/VERIFICATION.md` where it can be specific.
@@ -1,200 +0,0 @@
1
- # Live verification — redis / rabbitmq
2
-
3
- `queue-aiops` is exercised by a **mock-only** test suite (`uv run pytest`, no real
4
- broker). It has **not** yet been validated end-to-end against a live redis instance or
5
- rabbitmq broker. This document says exactly what the mock suite already guarantees, and
6
- what a live run has to prove before anyone may describe this tool as verified against a
7
- real broker.
8
-
9
- It is deliberately checklist-shaped so the result is reproducible and auditable — not a
10
- subjective "seems fine".
11
-
12
- ## What the mock suite already guarantees
13
-
14
- - Every module imports; the CLI builds; **all 28 MCP tools** carry the `@governed_tool`
15
- harness marker (`tests/test_smoke.py`, which also asserts the tool count and that
16
- `__version__` matches `pyproject.toml`).
17
- - The four flagship analyses (`redis_memory_pressure_rca`, `redis_latency_rca`,
18
- `rabbitmq_queue_backlog_rca`, `connection_churn_analysis`) are unit-tested against
19
- synthetic telemetry: each cause classification fires on the right signal
20
- (noeviction-near-limit vs active eviction vs fragmentation vs swapping; O(N) command
21
- patterns and fork/AOF stalls; no-consumers vs unacked pile-up vs rate deficit),
22
- findings cite the measured number, and partial/missing fields do not crash.
23
- - The **platform registry** resolves each tool to the correct redis (RESP, via the
24
- `redis` Python client) or rabbitmq (management HTTP API, HTTP Basic) shape.
25
- - **Safety invariants** are tested: the redis surface is a typed command **allow-list**
26
- with no generic passthrough; big-key sampling is `SCAN`-based under a hard budget and
27
- **never** issues `KEYS *`; and rabbitmq path segments (queue/policy names, and the
28
- default vhost `/` → `%2F`) are percent-encoded centrally.
29
- - Reversible writes record a faithful **inverse** undo descriptor built from a fetched
30
- before-state (`redis_config_set` restores the prior value from `CONFIG GET`;
31
- `set_policy` / `delete_policy` restore the captured definition; `declare_queue`'s undo
32
- deletes it only when newly created). `purge_queue` declares **no** undo;
33
- `delete_queue`'s undo re-declares the captured definition and the tests assert that
34
- **messages are not claimed to be restored**.
35
- - Governance persistence is tested against a real on-disk SQLite audit DB: calls land as
36
- rows, failures record `status=error` and no undo, and the secure-by-default approver
37
- gate refuses high-risk ops when no `rules.yaml` exists.
38
-
39
- What it does **not** guarantee: that the concrete `INFO` field names, `SLOWLOG` entry
40
- shape, and rabbitmq management-API JSON match a real broker build. Those are modelled
41
- from each project's public documentation and are the **largest verification debt in this
42
- repo** — `INFO` field names in particular have shifted across redis 5/6/7.
43
-
44
- ## Prerequisites for a live run
45
-
46
- Both platforms are free and trivially containerised:
47
-
48
- ```bash
49
- docker run -d --name redis-verify -p 6379:6379 redis:7 \
50
- redis-server --maxmemory 128mb --maxmemory-policy noeviction
51
- docker run -d --name rabbit-verify -p 5672:5672 -p 15672:15672 rabbitmq:3-management
52
- ```
53
-
54
- The rabbitmq **management plugin** must be enabled (the `-management` image does this),
55
- and the API user needs the `monitoring` or `management` tag.
56
-
57
- Use a **throwaway broker with throwaway data**. The checklist purges and deletes queues,
58
- kills clients, and changes the running config — `purge_queue` destroys real messages and
59
- records **no undo**. Never point it at a broker carrying traffic you care about.
60
-
61
- Set the lab up so the reads have something to find:
62
-
63
- - Fill redis past 80% of a small `maxmemory` and leave the policy at `noeviction`, so the
64
- most dangerous finding is reachable.
65
- - Create a few deliberately oversized keys (a large hash and a large list).
66
- - Run some `KEYS`/`SMEMBERS` calls so the SLOWLOG has genuine O(N) entries.
67
- - On rabbitmq, create three queues: one with **no consumer** and messages published, one
68
- with a consumer that **never acks**, and one healthy — the three backlog causes.
69
-
70
- ```bash
71
- uv tool install queue-aiops
72
- queue-aiops init # wizard: pick platform, optional encrypted secret
73
- ```
74
-
75
- Record the exact versions tested (e.g. "redis 7.4, rabbitmq 3.13") — a tick is only
76
- meaningful with the build it was ticked against.
77
-
78
- ## Verification checklist
79
-
80
- Tick every box, **per platform**. A box that cannot be ticked is a verification gap —
81
- record it, do not silently pass.
82
-
83
- ### 1. Connectivity (the fastest live gate)
84
- - [ ] `queue-aiops doctor` → green on each target: config parsed, secret store unlocks,
85
- and a real `PING` / `/api/overview` returns.
86
- - [ ] `queue-aiops doctor --skip-auth` → passes offline (config/secret checks only).
87
- - [ ] An **auth-less** lab redis connects with no stored secret (this is a supported
88
- configuration and a common mock-vs-reality divergence).
89
- - [ ] One config spanning both platforms works with `--target` switching.
90
-
91
- ### 2. Reads return real, well-shaped data
92
- - [ ] `queue-aiops overview` → real version/role, memory posture, client counts, ops/sec,
93
- hit rate and key count (redis); version, queue/message totals, connection/channel/
94
- consumer counts and node alarms (rabbitmq). Confirm a **partial** failure degrades
95
- into the `errors` list rather than failing the whole call.
96
- - [ ] `queue-aiops redis info` / `redis memory` → fields match `redis-cli INFO`
97
- **on each of redis 6 and 7**, since field names have moved between versions.
98
- - [ ] `queue-aiops redis clients` → matches `CLIENT LIST`; `redis keyspace` matches
99
- `DBSIZE` per database.
100
- - [ ] `queue-aiops redis slowlog --limit 50` → matches `SLOWLOG GET`, with timings
101
- parsed correctly.
102
- - [ ] `queue-aiops redis config-get maxmemory*` → glob patterns work and match
103
- `CONFIG GET`.
104
- - [ ] `queue-aiops redis bigkeys --count 500` → finds the oversized keys you planted, and
105
- reports a **coverage %** consistent with the keyspace size. Confirm with
106
- `MONITOR` or the slowlog that it issued `SCAN`, **never** `KEYS *` — this is the
107
- single most important safety check in the file.
108
- - [ ] `queue-aiops rabbitmq queues` / `queue <name>` / `connections` / `channels` /
109
- `policies` / `nodes` → match the management UI, including ready-vs-unacked splits.
110
- - [ ] A queue and a policy whose **names need percent-encoding** (a `/`, a space, a `%`)
111
- are read correctly, and the default vhost `/` resolves as `%2F`.
112
-
113
- ### 3. The analyses are right, not just non-crashing
114
- - [ ] With redis near `maxmemory` under `noeviction`,
115
- `queue-aiops analyze memory --used-pct 85` raises the **noeviction-near-limit**
116
- finding and cites the real used/max numbers. Switch to `allkeys-lru`, drive
117
- evictions, and confirm the finding changes to active eviction.
118
- - [ ] Fragmentation vs swapping are distinguished correctly (do not accept "flagged
119
- something" — check it names the right one).
120
- - [ ] `queue-aiops analyze latency --slow-us 10000` digests the planted O(N) entries by
121
- **command pattern** and suggests the incremental variant (`KEYS` → `SCAN`).
122
- - [ ] Trigger a background save on a large dataset; the fork/AOF stall signal appears.
123
- - [ ] `queue-aiops analyze backlog --top 20` classifies each of your three planted
124
- queues correctly: **no consumers**, **unacked pile-up**, and healthy.
125
- - [ ] Drive the node past its memory watermark; the alarm is reported as blocking **all**
126
- publishers, and outranks the per-queue findings.
127
- - [ ] `queue-aiops analyze churn` on both platforms pins reconnect churn to the right
128
- client source.
129
-
130
- ### 4. A reversible write + its undo (governance closes the loop)
131
- - [ ] `queue-aiops redis config-set maxmemory-policy allkeys-lru --dry-run` → prints the
132
- call, `CONFIG GET` shows the value unchanged.
133
- - [ ] `queue-aiops redis config-set maxmemory-policy allkeys-lru` → the running config
134
- changes, the result carries an `_undo_id`, and a row lands in
135
- `~/.queue-aiops/audit.db`.
136
- - [ ] `queue-aiops undo list` shows it; `undo apply <id>` restores the **prior** policy —
137
- verify starting from a non-default value, where a naive undo would wrongly set
138
- `noeviction`.
139
- - [ ] `queue-aiops rabbitmq set-policy <name> <pattern> '{"max-length":1000}'` over an
140
- **existing** policy, then `undo apply` → the prior definition returns intact (not
141
- a deletion).
142
- - [ ] `queue-aiops rabbitmq declare-queue <new> --dry-run` then for real, then
143
- `undo apply` → the newly created queue is deleted. Repeat against a queue that
144
- **already existed**: the undo must **not** delete it.
145
-
146
- ### 5. Irreversible / asymmetric writes behave as declared
147
- - [ ] `queue-aiops rabbitmq purge <queue> --dry-run` → reports the message count about to
148
- be destroyed; the real run destroys exactly that many and records **no** undo.
149
- - [ ] `queue-aiops rabbitmq delete-queue <queue>` on a queue **with messages**, then
150
- `undo apply` → the queue is re-declared with the right durable/auto-delete flags,
151
- and the **messages are gone**. Confirm the tool never claimed otherwise.
152
- - [ ] `queue-aiops redis kill-client --addr <ip:port>` drops that connection, records no
153
- undo, and is audited.
154
-
155
- ### 6. Governance actually gates
156
- - [ ] With no `~/.queue-aiops/rules.yaml`, `purge_queue` and `delete_queue` are
157
- **refused** unless `QUEUE_AUDIT_APPROVED_BY` is set (secure-by-default); with it
158
- plus `QUEUE_AUDIT_RATIONALE`, both appear in the audit row.
159
- - [ ] A tight poll loop trips the runaway budget guard rather than hammering the broker.
160
- - [ ] A failed call (nonexistent queue) is audited `status=error` and records no undo.
161
- - [ ] Attempting a redis command outside the typed allow-list is rejected **before** the
162
- client sends anything — confirm against the live server that nothing executes.
163
-
164
- ### 7. Cleanup
165
- - [ ] Restore every config value you changed, delete the throwaway queues and policies,
166
- and flush the lab redis.
167
- - [ ] `queue-aiops overview` matches the baseline you captured before starting.
168
- - [ ] Skim `~/.queue-aiops/audit.db` — every write is there with the right risk tier.
169
-
170
- ## Criteria to consider it live-verified
171
-
172
- All of the following must hold:
173
-
174
- 1. Every box above is ticked against **both** a real redis and a real rabbitmq, with the
175
- exact builds recorded (e.g. "redis 7.4 + rabbitmq 3.13"), and the redis reads
176
- re-checked on at least **two major redis versions** given the `INFO` field drift.
177
- 2. Every field-name or API-shape mismatch found is **fixed and covered by a regression
178
- test**, so the mock suite would now catch it.
179
- 3. Section 4 passed, including the asymmetric `declare_queue` case (undo must delete only
180
- a queue it created). Recording an undo descriptor is not the same as the undo
181
- working, and this product line has shipped broken undo pairs before.
182
- 4. The big-key sampler was confirmed **live** to issue `SCAN` under budget and never
183
- `KEYS *` — a mock cannot prove this, and getting it wrong stalls a production redis.
184
- 5. The run is written up in the release notes / product-line memory with the date and
185
- package version, matching how the line records its other live-verified tools.
186
-
187
- Until then, this repo says only what is true: mock-validated, live-unverified. Claiming
188
- otherwise would break that promise.
189
-
190
- ## Notes for maintainers
191
-
192
- - `queue-aiops doctor` is the single fastest live entry point; start there.
193
- - Weight the run toward **redis `INFO` field names** and **rabbitmq percent-encoded path
194
- segments** — those are the two places the mocks are least able to model.
195
- - Managed cloud queue services (ElastiCache, Amazon MQ, CloudAMQP, …) are a **separate**
196
- verification target: wire-compatible reads may work, but `CONFIG SET` and
197
- `CLIENT KILL` are commonly restricted. Do not infer managed support from a self-hosted
198
- run.
199
- - Add this tool's result to the product-line verification ledger once green, so the
200
- central "verification debt" list stays accurate.
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes