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.
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/.claude-plugin/plugin.json +1 -1
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/.mcp.json +1 -1
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/CHANGELOG.md +22 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/PKG-INFO +1 -1
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/openclaw.plugin.json +1 -1
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/package.json +1 -1
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/pyproject.toml +1 -1
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/ops/analysis.py +5 -1
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/server.json +2 -2
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/skills/queue-aiops/SKILL.md +2 -2
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/skills/queue-aiops/references/agent-guardrails.md +9 -1
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_analysis.py +68 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/uv.lock +1 -1
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/.github/workflows/mcp-publish.yml +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/.github/workflows/publish.yml +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/.gitignore +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/Dockerfile +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/LICENSE +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/README.md +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/RELEASE_NOTES.md +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/SECURITY.md +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/docs/VERIFICATION.md +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/glama.json +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/mcp_server/__init__.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/mcp_server/_shared.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/mcp_server/server.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/mcp_server/tools/__init__.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/mcp_server/tools/analysis.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/mcp_server/tools/overview.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/mcp_server/tools/rabbit_reads.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/mcp_server/tools/redis_reads.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/mcp_server/tools/undo.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/mcp_server/tools/writes.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/__init__.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/cli/__init__.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/cli/_common.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/cli/_root.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/cli/analyze.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/cli/doctor.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/cli/init.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/cli/overview.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/cli/rabbit_cmds.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/cli/redis_cmds.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/cli/secret.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/cli/undo.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/config.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/connection.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/doctor.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/governance/__init__.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/governance/audit.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/governance/budget.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/governance/decorators.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/governance/outcome.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/governance/paths.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/governance/patterns.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/governance/policy.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/governance/sanitize.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/governance/undo.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/ops/__init__.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/ops/_util.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/ops/overview.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/ops/rabbit_reads.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/ops/redis_reads.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/ops/writes.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/platform.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/queue_aiops/secretstore.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/skills/queue-aiops/references/capabilities.md +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/skills/queue-aiops/references/cli-reference.md +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/skills/queue-aiops/references/setup-guide.md +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/smithery.yaml +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/conftest.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/fakes.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_cli_audited.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_cli_governed_exit.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_cli_rabbit_writes.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_cli_reads.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_cli_secret.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_cli_writes.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_connection.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_doctor.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_gov_audit.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_gov_decorators.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_gov_patterns.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_gov_policy.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_governance_persistence.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_init.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_int_fidelity.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_mcp_reads.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_optional_fields.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_platform.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_reads.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_secretstore.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_self_kill.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_self_lockout.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_smoke.py +0 -0
- {queue_aiops-0.9.3 → queue_aiops-0.9.4}/tests/test_undo_executor.py +0 -0
- {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.
|
|
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",
|
|
@@ -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
|
+
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
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "queue-aiops"
|
|
7
|
-
version = "0.9.
|
|
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":
|
|
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.
|
|
10
|
+
"version": "0.9.4",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "pypi",
|
|
14
14
|
"identifier": "queue-aiops",
|
|
15
|
-
"version": "0.9.
|
|
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` →
|
|
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
|
-
| "
|
|
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)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|