queue-aiops 0.9.3__tar.gz → 0.9.4__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 (97) hide show
  1. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/.claude-plugin/plugin.json +1 -1
  2. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/.mcp.json +1 -1
  3. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/CHANGELOG.md +22 -0
  4. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/PKG-INFO +1 -1
  5. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/openclaw.plugin.json +1 -1
  6. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/package.json +1 -1
  7. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/pyproject.toml +1 -1
  8. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/ops/analysis.py +5 -1
  9. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/server.json +2 -2
  10. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/skills/queue-aiops/SKILL.md +2 -2
  11. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/skills/queue-aiops/references/agent-guardrails.md +9 -1
  12. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_analysis.py +68 -0
  13. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/uv.lock +1 -1
  14. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/.github/workflows/mcp-publish.yml +0 -0
  15. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/.github/workflows/publish.yml +0 -0
  16. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/.gitignore +0 -0
  17. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/Dockerfile +0 -0
  18. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/LICENSE +0 -0
  19. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/README.md +0 -0
  20. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/RELEASE_NOTES.md +0 -0
  21. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/SECURITY.md +0 -0
  22. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/docs/VERIFICATION.md +0 -0
  23. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/glama.json +0 -0
  24. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/mcp_server/__init__.py +0 -0
  25. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/mcp_server/_shared.py +0 -0
  26. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/mcp_server/server.py +0 -0
  27. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/mcp_server/tools/__init__.py +0 -0
  28. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/mcp_server/tools/analysis.py +0 -0
  29. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/mcp_server/tools/overview.py +0 -0
  30. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/mcp_server/tools/rabbit_reads.py +0 -0
  31. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/mcp_server/tools/redis_reads.py +0 -0
  32. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/mcp_server/tools/undo.py +0 -0
  33. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/mcp_server/tools/writes.py +0 -0
  34. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/__init__.py +0 -0
  35. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/cli/__init__.py +0 -0
  36. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/cli/_common.py +0 -0
  37. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/cli/_root.py +0 -0
  38. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/cli/analyze.py +0 -0
  39. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/cli/doctor.py +0 -0
  40. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/cli/init.py +0 -0
  41. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/cli/overview.py +0 -0
  42. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/cli/rabbit_cmds.py +0 -0
  43. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/cli/redis_cmds.py +0 -0
  44. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/cli/secret.py +0 -0
  45. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/cli/undo.py +0 -0
  46. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/config.py +0 -0
  47. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/connection.py +0 -0
  48. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/doctor.py +0 -0
  49. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/governance/__init__.py +0 -0
  50. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/governance/audit.py +0 -0
  51. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/governance/budget.py +0 -0
  52. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/governance/decorators.py +0 -0
  53. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/governance/outcome.py +0 -0
  54. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/governance/paths.py +0 -0
  55. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/governance/patterns.py +0 -0
  56. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/governance/policy.py +0 -0
  57. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/governance/sanitize.py +0 -0
  58. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/governance/undo.py +0 -0
  59. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/ops/__init__.py +0 -0
  60. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/ops/_util.py +0 -0
  61. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/ops/overview.py +0 -0
  62. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/ops/rabbit_reads.py +0 -0
  63. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/ops/redis_reads.py +0 -0
  64. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/ops/writes.py +0 -0
  65. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/platform.py +0 -0
  66. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/secretstore.py +0 -0
  67. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/skills/queue-aiops/references/capabilities.md +0 -0
  68. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/skills/queue-aiops/references/cli-reference.md +0 -0
  69. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/skills/queue-aiops/references/setup-guide.md +0 -0
  70. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/smithery.yaml +0 -0
  71. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/conftest.py +0 -0
  72. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/fakes.py +0 -0
  73. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_cli_audited.py +0 -0
  74. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_cli_governed_exit.py +0 -0
  75. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_cli_rabbit_writes.py +0 -0
  76. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_cli_reads.py +0 -0
  77. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_cli_secret.py +0 -0
  78. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_cli_writes.py +0 -0
  79. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_connection.py +0 -0
  80. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_doctor.py +0 -0
  81. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_gov_audit.py +0 -0
  82. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_gov_decorators.py +0 -0
  83. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_gov_patterns.py +0 -0
  84. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_gov_policy.py +0 -0
  85. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_governance_persistence.py +0 -0
  86. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_init.py +0 -0
  87. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_int_fidelity.py +0 -0
  88. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_mcp_reads.py +0 -0
  89. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_optional_fields.py +0 -0
  90. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_platform.py +0 -0
  91. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_reads.py +0 -0
  92. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_secretstore.py +0 -0
  93. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_self_kill.py +0 -0
  94. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_self_lockout.py +0 -0
  95. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_smoke.py +0 -0
  96. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_undo_executor.py +0 -0
  97. {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_writes.py +0 -0
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://anthropic.com/claude-code/plugin.schema.json",
3
3
  "name": "queue-aiops",
4
4
  "displayName": "Queue AIops",
5
- "version": "0.9.3",
5
+ "version": "0.9.4",
6
6
  "description": "Governed redis + rabbitmq ops: memory/latency/backlog/churn RCA, policies. 28 tools.",
7
7
  "author": {
8
8
  "name": "Wei Zhou",
@@ -5,7 +5,7 @@
5
5
  "command": "uvx",
6
6
  "args": [
7
7
  "--from",
8
- "queue-aiops==0.9.3",
8
+ "queue-aiops==0.9.4",
9
9
  "queue-aiops-mcp"
10
10
  ]
11
11
  }
@@ -1,5 +1,27 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.9.4 — 2026-09-16
4
+
5
+ ### Fixed
6
+ - `redis_memory_pressure_rca`'s top-level `pressure` flag no longer decides by
7
+ comparing another finding's `cause` against the literal sentence "Healthy —
8
+ within thresholds". The flag is behaviourally unchanged, but rewording that
9
+ sentence — or normalising its em dash in a style sweep — would have reported
10
+ pressure on a healthy broker. It now reads the measured condition captured
11
+ before the healthy placeholder is appended, and an AST-based structural test
12
+ pins the whole package against a decision being taken on a finding's prose —
13
+ either operand order, the literal hoisted into a constant, `.get()`, `in`,
14
+ `startswith`, slicing and `.lower()` are all rejected.
15
+ - `agent-guardrails.md` and `SKILL.md` claimed the four RCAs "rank findings
16
+ worst-first" and that "priority is in the payload, not implied by list
17
+ position". Neither was ever true here: there is no `rank` key, and findings are
18
+ appended in the order the checks run. The claim sat in the table headed "what
19
+ the tool enforces — do not waste prompt budget on these", so it actively told
20
+ operators to drop the prompt that would have compensated. A broker at 12 % of
21
+ `maxmemory` with 35 GiB of oversized keys reports the fragmentation finding
22
+ first, purely because fragmentation is checked earlier. The docs now say order
23
+ is not severity, and point at the `evidence` number each finding does carry.
24
+
3
25
  ## v0.9.3 — 2026-09-15
4
26
 
5
27
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: queue-aiops
3
- Version: 0.9.3
3
+ Version: 0.9.4
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 Zhou <zhouwei008@gmail.com>
6
6
  License-Expression: MIT
@@ -11,7 +11,7 @@
11
11
  "command": "uvx",
12
12
  "args": [
13
13
  "--from",
14
- "queue-aiops==0.9.3",
14
+ "queue-aiops==0.9.4",
15
15
  "queue-aiops-mcp"
16
16
  ]
17
17
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zw008/queue-aiops",
3
- "version": "0.9.3",
3
+ "version": "0.9.4",
4
4
  "description": "Governed redis + rabbitmq ops: memory/latency/backlog/churn RCA, policies. 28 tools.",
5
5
  "private": true,
6
6
  "license": "MIT",
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "queue-aiops"
7
- version = "0.9.3"
7
+ version = "0.9.4"
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"
@@ -126,6 +126,10 @@ def redis_memory_pressure_rca(
126
126
  "evidence": {"bigKeys": big[:10]},
127
127
  })
128
128
 
129
+ # Captured before the healthy placeholder is appended. The flag reports the measured
130
+ # condition — did any check fire — and is never derived from another finding's prose:
131
+ # a reworded sentence would otherwise report pressure on a healthy broker.
132
+ pressure = bool(findings)
129
133
  if not findings:
130
134
  findings.append({
131
135
  "cause": "Healthy — within thresholds",
@@ -133,7 +137,7 @@ def redis_memory_pressure_rca(
133
137
  "evidence": {"usedPctOfMax": used_of_max, "fragmentationRatio": frag},
134
138
  })
135
139
  return {
136
- "pressure": any(f["cause"] != "Healthy — within thresholds" for f in findings),
140
+ "pressure": pressure,
137
141
  "usedPctOfMax": used_of_max,
138
142
  "maxmemoryPolicy": policy,
139
143
  "fragmentationRatio": frag,
@@ -7,12 +7,12 @@
7
7
  "url": "https://github.com/AIops-tools/Queue-AIops",
8
8
  "source": "github"
9
9
  },
10
- "version": "0.9.3",
10
+ "version": "0.9.4",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "pypi",
14
14
  "identifier": "queue-aiops",
15
- "version": "0.9.3",
15
+ "version": "0.9.4",
16
16
  "transport": {
17
17
  "type": "stdio"
18
18
  }
@@ -121,8 +121,8 @@ skill.
121
121
  works before you read numbers off it.
122
122
  2. `queue-aiops redis memory` → used vs `maxmemory`, the eviction policy in force, and
123
123
  the fragmentation ratio, straight from `INFO memory`.
124
- 3. `queue-aiops analyze memory --used-pct 85` → ranked findings, each citing its measured
125
- number: **noeviction near the limit** (the dangerous one — writes will start failing
124
+ 3. `queue-aiops analyze memory --used-pct 85` → findings in check order, each citing its measured
125
+ number (order is not severity — weigh them all): **noeviction near the limit** (the dangerous one — writes will start failing
126
126
  with OOM rather than evicting), active eviction in progress, fragmentation versus real
127
127
  swapping, and oversized keys.
128
128
  4. `queue-aiops redis bigkeys --count 500` → a SCAN-budgeted sample of the largest keys,
@@ -31,7 +31,7 @@ What the tool *does* guarantee is that you can always see what happened:
31
31
  |---|---|
32
32
  | "Don't invent a value when a field is missing" | RabbitMQ omits `idle_since` for an active queue; a Redis primary reports no `master_link_status`; an unnamed client has no name. Those come back as `null`, never as `""`. |
33
33
  | "Tell me if the output was cut off" | `redis_slowlog`, `redis_clients`, `redis_big_keys`, `list_queues`, `list_connections`, `list_channels` and `list_policies` all return `{"<items>": [...], "returned": N, "limit": L, "truncated": true/false}`. Truncation is measured — one extra entry is requested from Redis — not guessed from a length coincidence. |
34
- | "Preserve the ordering / tell me what's most urgent" | `rabbitmq_queue_backlog_rca`, `redis_memory_pressure_rca`, `redis_latency_rca` and `connection_churn_analysis` rank findings worst-first, each carrying the measured number it was based on. Priority is in the payload, not implied by list position. |
34
+ | "Make it show the number it judged on" | Every finding from `rabbitmq_queue_backlog_rca`, `redis_memory_pressure_rca`, `redis_latency_rca` and `connection_churn_analysis` carries the measured value it was based on under `evidence`, so a claim can always be checked against a number rather than taken on the model's word. |
35
35
  | "Don't run KEYS * on production" | `redis_big_keys` uses SCAN under a hard key budget and sizes only an evenly-spaced subset with MEMORY USAGE. There is no code path that can issue `KEYS *`. `coveragePct` reports how partial the walk was. |
36
36
  | "Confirm before anything destructive" | `delete_queue` and `purge_queue` require a `--dry-run`-able preview plus double confirmation at the CLI. |
37
37
  | "Log what you did" | Every governed call is audited to `~/.queue-aiops/audit.db` regardless of what the model says it did — and the CLI writes the same row the MCP path does, so there is no unaudited entry point. |
@@ -40,6 +40,14 @@ What the tool *does* guarantee is that you can always see what happened:
40
40
  ## What still needs a prompt
41
41
 
42
42
  These are model-behaviour problems the harness cannot fix from the outside.
43
+
44
+ ⚠️ **Finding order is not priority.** These RCAs append findings in the order the checks
45
+ run, not worst-first, and there is no `rank` key in the payload. A broker at 12 % of
46
+ `maxmemory` with 35 GiB of oversized keys reports the fragmentation finding first purely
47
+ because fragmentation is checked before big keys. Do not let the model treat `findings[0]`
48
+ as the headline — make it weigh every finding's `evidence` and say which one it acted on.
49
+ (Earlier versions of this file claimed these lists were ranked worst-first. They never were.)
50
+
43
51
  Copy this into your agent's system prompt:
44
52
 
45
53
  ```text
@@ -2,6 +2,9 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import ast
6
+ from pathlib import Path
7
+
5
8
  import pytest
6
9
 
7
10
  from queue_aiops.ops import analysis as ops
@@ -200,3 +203,68 @@ def test_churn_healthy_reports_metrics():
200
203
  })
201
204
  assert out["findings"][0]["action"] == "No action needed."
202
205
  assert out["metrics"]["channels"] == 8
206
+
207
+
208
+ @pytest.mark.unit
209
+ def test_no_decision_is_derived_from_a_findings_display_text():
210
+ """A rule's input is never another rule's display text.
211
+
212
+ `pressure` used to be `any(f["cause"] != "Healthy — within thresholds" ...)`, so
213
+ rewording that sentence — or normalising its em dash in a style sweep — would have
214
+ reported pressure on a healthy broker. The check is AST-based on purpose: a regex on
215
+ the source missed the shape that produced the original incident in another repo
216
+ (`"critical blocker" in f["signal"]`, literal on the left), and also fired on prose
217
+ inside comments and docstrings. Walking the tree catches either operand order, the
218
+ literal hoisted into a constant, `.get()`, `.lower()`, slicing and `startswith`, and
219
+ ignores comments entirely.
220
+ """
221
+ offenders = []
222
+ for path in _scanned_sources():
223
+ tree = ast.parse(path.read_text(encoding="utf-8"))
224
+ for node in ast.walk(tree):
225
+ if isinstance(node, ast.Compare):
226
+ operands = [node.left, *node.comparators]
227
+ if any(_reads_display_text(o) for o in operands) and not _is_none_check(node):
228
+ offenders.append(f"{path.name}:{node.lineno}: {ast.unparse(node)}")
229
+ elif isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute) \
230
+ and node.func.attr in _TEXT_PREDICATES and _reads_display_text(node.func.value):
231
+ offenders.append(f"{path.name}:{node.lineno}: {ast.unparse(node)}")
232
+ assert offenders == [], ("a decision is being derived from a finding's prose:\n"
233
+ + "\n".join(offenders))
234
+
235
+
236
+ _DISPLAY_KEYS = {"cause", "action", "signal", "note", "summary"}
237
+ _TEXT_PREDICATES = {"startswith", "endswith", "count", "find", "index"}
238
+
239
+
240
+ def _scanned_sources():
241
+ """Every module that could post-process a finding — not just the one that builds them.
242
+
243
+ `governance/` is excluded: it is vendored verbatim across the line and held byte-identical
244
+ by the drift gate, so it is not this repo's to edit.
245
+ """
246
+ root = Path(ops.__file__).parents[2]
247
+ found = sorted(root.glob("queue_aiops/**/*.py")) + sorted(root.glob("mcp_server/**/*.py"))
248
+ files = [p for p in found if "governance" not in p.parts]
249
+ assert len(files) > 10, f"source sweep found only {len(files)} modules — the glob is wrong"
250
+ return files
251
+
252
+
253
+ def _reads_display_text(node):
254
+ """True if evaluating this expression reads a finding's display field, however wrapped."""
255
+ for sub in ast.walk(node):
256
+ if isinstance(sub, ast.Subscript) and isinstance(sub.slice, ast.Constant) \
257
+ and sub.slice.value in _DISPLAY_KEYS:
258
+ return True
259
+ if isinstance(sub, ast.Call) and isinstance(sub.func, ast.Attribute) \
260
+ and sub.func.attr == "get" and sub.args \
261
+ and isinstance(sub.args[0], ast.Constant) \
262
+ and sub.args[0].value in _DISPLAY_KEYS:
263
+ return True
264
+ return False
265
+
266
+
267
+ def _is_none_check(node):
268
+ """`f["cause"] is None` is a presence test, not a decision taken on the prose."""
269
+ return all(isinstance(op, (ast.Is, ast.IsNot)) for op in node.ops) and all(
270
+ isinstance(c, ast.Constant) and c.value is None for c in node.comparators)
@@ -812,7 +812,7 @@ wheels = [
812
812
 
813
813
  [[package]]
814
814
  name = "queue-aiops"
815
- version = "0.9.3"
815
+ version = "0.9.4"
816
816
  source = { editable = "." }
817
817
  dependencies = [
818
818
  { name = "cryptography" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes