proxy-aiops 0.2.0__tar.gz → 0.3.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.
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/PKG-INFO +1 -1
- proxy_aiops-0.3.0/RELEASE_NOTES.md +27 -0
- proxy_aiops-0.3.0/docs/VERIFICATION.md +32 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/proxy_aiops/ops/_util.py +24 -1
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/proxy_aiops/ops/routes.py +12 -4
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/proxy_aiops/ops/services.py +5 -5
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/pyproject.toml +1 -1
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/server.json +2 -2
- proxy_aiops-0.3.0/tests/test_int_fidelity.py +34 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/uv.lock +1 -1
- proxy_aiops-0.2.0/RELEASE_NOTES.md +0 -48
- proxy_aiops-0.2.0/docs/VERIFICATION.md +0 -191
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/.github/workflows/mcp-publish.yml +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/.github/workflows/publish.yml +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/.gitignore +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/CHANGELOG.md +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/LICENSE +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/README.md +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/SECURITY.md +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/mcp_server/__init__.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/mcp_server/_shared.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/mcp_server/server.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/mcp_server/tools/__init__.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/mcp_server/tools/analysis.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/mcp_server/tools/certs.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/mcp_server/tools/configread.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/mcp_server/tools/routes.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/mcp_server/tools/services.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/mcp_server/tools/status.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/mcp_server/tools/traffic.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/mcp_server/tools/undo.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/mcp_server/tools/writes.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/proxy_aiops/__init__.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/proxy_aiops/cli/__init__.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/proxy_aiops/cli/_common.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/proxy_aiops/cli/_root.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/proxy_aiops/cli/analyze.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/proxy_aiops/cli/certs.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/proxy_aiops/cli/configcmd.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/proxy_aiops/cli/doctor.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/proxy_aiops/cli/init.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/proxy_aiops/cli/overview.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/proxy_aiops/cli/routes.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/proxy_aiops/cli/secret.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/proxy_aiops/cli/server.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/proxy_aiops/cli/services.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/proxy_aiops/cli/undo.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/proxy_aiops/config.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/proxy_aiops/connection.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/proxy_aiops/doctor.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/proxy_aiops/governance/__init__.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/proxy_aiops/governance/audit.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/proxy_aiops/governance/budget.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/proxy_aiops/governance/decorators.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/proxy_aiops/governance/paths.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/proxy_aiops/governance/patterns.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/proxy_aiops/governance/policy.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/proxy_aiops/governance/readonly.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/proxy_aiops/governance/sanitize.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/proxy_aiops/governance/undo.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/proxy_aiops/ops/__init__.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/proxy_aiops/ops/analysis.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/proxy_aiops/ops/certs.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/proxy_aiops/ops/configread.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/proxy_aiops/ops/overview.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/proxy_aiops/ops/status.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/proxy_aiops/ops/traffic.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/proxy_aiops/ops/writes.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/proxy_aiops/platform.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/proxy_aiops/secretstore.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/skills/proxy-aiops/SKILL.md +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/skills/proxy-aiops/references/agent-guardrails.md +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/skills/proxy-aiops/references/capabilities.md +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/skills/proxy-aiops/references/cli-reference.md +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/skills/proxy-aiops/references/setup-guide.md +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/smithery.yaml +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/tests/conftest.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/tests/test_analysis.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/tests/test_cli_reads.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/tests/test_cli_secret.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/tests/test_cli_writes.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/tests/test_connection.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/tests/test_doctor.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/tests/test_gov_audit.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/tests/test_gov_decorators.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/tests/test_gov_patterns.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/tests/test_gov_policy.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/tests/test_governance_persistence.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/tests/test_init.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/tests/test_mcp_read_tools.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/tests/test_ops_extra.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/tests/test_optional_fields.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/tests/test_platform.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/tests/test_readonly.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/tests/test_reads.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/tests/test_secretstore.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/tests/test_smoke.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/tests/test_undo_executor.py +0 -0
- {proxy_aiops-0.2.0 → proxy_aiops-0.3.0}/tests/test_writes.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: proxy-aiops
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Governed AI-ops for Traefik + Caddy + HAProxy reverse proxies / load balancers: routes, services, upstream health, TLS cert expiry sweep, 5xx error-rate RCA, route conflict analysis, and governed writes (caddy config, haproxy server state/weight) with a built-in governance harness (audit, budget, undo, risk tiers)
|
|
5
5
|
Author-email: wei <zhouwei008@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Release notes — proxy-aiops 0.3.0
|
|
2
|
+
|
|
3
|
+
Previous release: 0.2.0.
|
|
4
|
+
|
|
5
|
+
## Fixed: route priority lost precision and collapsed distinct values
|
|
6
|
+
|
|
7
|
+
Traefik route priority is an int64. Routing it through the float helper rendered it
|
|
8
|
+
in scientific notation (`9.223372036854776e+18`) *and* lost precision, because a
|
|
9
|
+
float64 cannot represent values near 2**63 exactly.
|
|
10
|
+
|
|
11
|
+
The practical consequence, seen on a live Traefik: two routers with **different**
|
|
12
|
+
priorities (…806 and …805) displayed as the **same** number. Route priority decides
|
|
13
|
+
matching order, so this actively misleads anyone debugging which route wins.
|
|
14
|
+
|
|
15
|
+
Integer quantities — route priority, server weight, request totals, session counts —
|
|
16
|
+
now use an exact `as_int` that returns an existing `int` untouched instead of
|
|
17
|
+
round-tripping it through float64. Genuinely fractional values are unchanged.
|
|
18
|
+
|
|
19
|
+
If you parse these fields, the JSON changes from `9.223372036854776e+18` to
|
|
20
|
+
`9223372036854775806`, and counters from `12.0` to `12`.
|
|
21
|
+
|
|
22
|
+
## Live-verified
|
|
23
|
+
|
|
24
|
+
Against **Traefik 3.2.5** and **Caddy 2**: reads cross-checked against each proxy's
|
|
25
|
+
own API, plus the three analyses. See [docs/VERIFICATION.md](docs/VERIFICATION.md) —
|
|
26
|
+
**HAProxy remains unverified** (its Data Plane API setup was not built for this
|
|
27
|
+
round) and is now the largest gap here.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Live verification status
|
|
2
|
+
|
|
3
|
+
This document records what has and has not been validated against real reverse
|
|
4
|
+
proxies, so the maturity claim is auditable rather than a vibe.
|
|
5
|
+
|
|
6
|
+
## Already live-verified ✅ — Traefik 3.2.5 and Caddy 2 (2026-07-20)
|
|
7
|
+
|
|
8
|
+
- `doctor` against both live endpoints (unauthenticated admin APIs, as the tool
|
|
9
|
+
documents for traefik/caddy).
|
|
10
|
+
- Reads cross-checked against each proxy's own API: `overview`, `routes list`,
|
|
11
|
+
`services list`, and the three analyses (`analyze health/errors/conflicts`).
|
|
12
|
+
Router counts and names matched `/api/http/routers` (Traefik) and
|
|
13
|
+
`/config/apps/http/servers/...` (Caddy) exactly.
|
|
14
|
+
|
|
15
|
+
**A real bug was found and fixed by this run**: route priority is an int64 in
|
|
16
|
+
Traefik, and routing it through the float helper both rendered it in scientific
|
|
17
|
+
notation (`9.223372036854776e+18`) *and* lost precision. Two routers with
|
|
18
|
+
**different** priorities (…806 and …805) displayed as the **same** value. Route
|
|
19
|
+
priority decides matching order, so collapsing distinct values actively misleads
|
|
20
|
+
anyone debugging route precedence. Integer quantities now use an exact `as_int`
|
|
21
|
+
that never round-trips through float64.
|
|
22
|
+
|
|
23
|
+
## Not yet live-verified ⚠️
|
|
24
|
+
|
|
25
|
+
- **HAProxy** — the whole platform branch. It talks to the Data Plane API, which
|
|
26
|
+
needs a `program api` section in `haproxy.cfg` plus a dataplaneapi binary; that
|
|
27
|
+
setup was not built for this round. This is now the largest gap in this repo.
|
|
28
|
+
- **TLS / certificate expiry** (`certs`) against real certificates — both verified
|
|
29
|
+
instances served plaintext on a lab port.
|
|
30
|
+
- **Guarded config writes** (`config set/delete`) and their undo paths.
|
|
31
|
+
- Traffic/error-rate analyses against a proxy under real load (the lab instances
|
|
32
|
+
served no traffic, so `analyze errors` had nothing to rank).
|
|
@@ -71,13 +71,36 @@ def to_bool(value: Any) -> bool:
|
|
|
71
71
|
|
|
72
72
|
|
|
73
73
|
def num(value: Any) -> float:
|
|
74
|
-
"""Coerce a numeric cell to float; 0.0 when absent/non-numeric.
|
|
74
|
+
"""Coerce a numeric cell to float; 0.0 when absent/non-numeric.
|
|
75
|
+
|
|
76
|
+
Use only for genuinely fractional values (rates, durations). For integer
|
|
77
|
+
quantities — request counts, session counts, route priority — use
|
|
78
|
+
:func:`as_int`.
|
|
79
|
+
"""
|
|
75
80
|
try:
|
|
76
81
|
return float(value)
|
|
77
82
|
except (TypeError, ValueError):
|
|
78
83
|
return 0.0
|
|
79
84
|
|
|
80
85
|
|
|
86
|
+
def as_int(value: Any) -> int:
|
|
87
|
+
"""Coerce an integer quantity to ``int``; 0 when absent/non-numeric.
|
|
88
|
+
|
|
89
|
+
Route priority is an int64 in Traefik: routing it through :func:`num` both
|
|
90
|
+
rendered it in scientific notation (``9.223372036854776e+18``) *and* lost
|
|
91
|
+
precision, since a float64 cannot represent 2**63-1 exactly. Counters have
|
|
92
|
+
the same problem in miniature — a request total is never fractional.
|
|
93
|
+
"""
|
|
94
|
+
if isinstance(value, bool): # bool is an int subclass; not a quantity
|
|
95
|
+
return 0
|
|
96
|
+
if isinstance(value, int):
|
|
97
|
+
return value # already exact — never round-trip through float64
|
|
98
|
+
try:
|
|
99
|
+
return int(float(value))
|
|
100
|
+
except (TypeError, ValueError):
|
|
101
|
+
return 0
|
|
102
|
+
|
|
103
|
+
|
|
81
104
|
# ── Traefik rule-string parsing ──────────────────────────────────────────────
|
|
82
105
|
# Traefik encodes matching as a rule string: Host(`a.example`) &&
|
|
83
106
|
# PathPrefix(`/api`). We extract hosts and path prefixes for the static
|
|
@@ -17,7 +17,15 @@ from __future__ import annotations
|
|
|
17
17
|
|
|
18
18
|
from typing import Any
|
|
19
19
|
|
|
20
|
-
from proxy_aiops.ops._util import
|
|
20
|
+
from proxy_aiops.ops._util import (
|
|
21
|
+
as_int,
|
|
22
|
+
as_obj,
|
|
23
|
+
opt,
|
|
24
|
+
parse_rule_hosts,
|
|
25
|
+
parse_rule_paths,
|
|
26
|
+
pick,
|
|
27
|
+
s,
|
|
28
|
+
)
|
|
21
29
|
from proxy_aiops.platform import CADDY, TRAEFIK
|
|
22
30
|
|
|
23
31
|
MAX_ROUTES = 500
|
|
@@ -33,7 +41,7 @@ def _traefik_routes(conn: Any) -> list[dict]:
|
|
|
33
41
|
"platform": TRAEFIK,
|
|
34
42
|
"hosts": [s(h, 128) for h in parse_rule_hosts(rule)],
|
|
35
43
|
"paths": [s(p, 128) for p in parse_rule_paths(rule)],
|
|
36
|
-
"priority":
|
|
44
|
+
"priority": as_int(pick(r, "priority", default=0)),
|
|
37
45
|
"service": opt(pick(r, "service")),
|
|
38
46
|
# Traefik marks a TLS router with a "tls" object that is often
|
|
39
47
|
# EMPTY ({}) — presence, not truthiness, is the signal.
|
|
@@ -99,7 +107,7 @@ def _caddy_routes(conn: Any) -> list[dict]:
|
|
|
99
107
|
"platform": CADDY,
|
|
100
108
|
"hosts": [s(h, 128) for h in hosts],
|
|
101
109
|
"paths": [s(p, 128) for p in paths],
|
|
102
|
-
"priority": 0
|
|
110
|
+
"priority": 0,
|
|
103
111
|
"service": s(_caddy_service(handlers)),
|
|
104
112
|
"tls": tls_ish,
|
|
105
113
|
"enabled": True,
|
|
@@ -118,7 +126,7 @@ def _haproxy_routes(conn: Any) -> list[dict]:
|
|
|
118
126
|
"platform": "haproxy",
|
|
119
127
|
"hosts": [],
|
|
120
128
|
"paths": [],
|
|
121
|
-
"priority": 0
|
|
129
|
+
"priority": 0,
|
|
122
130
|
"service": opt(pick(r, "default_backend", default="")),
|
|
123
131
|
"tls": False,
|
|
124
132
|
"enabled": str(pick(r, "enabled", default="enabled")).lower() != "disabled",
|
|
@@ -14,7 +14,7 @@ from __future__ import annotations
|
|
|
14
14
|
from typing import Any
|
|
15
15
|
|
|
16
16
|
from proxy_aiops.ops import routes as route_ops
|
|
17
|
-
from proxy_aiops.ops._util import as_obj, num, opt, pick, s
|
|
17
|
+
from proxy_aiops.ops._util import as_int, as_obj, num, opt, pick, s
|
|
18
18
|
from proxy_aiops.platform import CADDY, HAPROXY, TRAEFIK, UnsupportedOperation
|
|
19
19
|
|
|
20
20
|
MAX_ROWS = 500
|
|
@@ -216,14 +216,14 @@ def _haproxy_stat_rows(conn: Any) -> list[dict]:
|
|
|
216
216
|
"status": status,
|
|
217
217
|
"checkStatus": opt(pick(stats, "check_status", default=""), 64),
|
|
218
218
|
"address": opt(pick(stats, "addr", default=""), 200),
|
|
219
|
-
"weight":
|
|
220
|
-
"requestsTotal":
|
|
219
|
+
"weight": as_int(pick(stats, "weight", default=0)),
|
|
220
|
+
"requestsTotal": as_int(pick(stats, "req_tot", default=0)),
|
|
221
221
|
"hrsp2xx": num(pick(stats, "hrsp_2xx", default=0)),
|
|
222
222
|
"hrsp4xx": num(pick(stats, "hrsp_4xx", default=0)),
|
|
223
223
|
"hrsp5xx": num(pick(stats, "hrsp_5xx", default=0)),
|
|
224
|
-
"currentSessions":
|
|
224
|
+
"currentSessions": as_int(pick(stats, "scur", default=0)),
|
|
225
225
|
"rate": num(pick(stats, "rate", default=0)),
|
|
226
|
-
"lastStatusChange":
|
|
226
|
+
"lastStatusChange": as_int(pick(stats, "lastchg", default=0)),
|
|
227
227
|
})
|
|
228
228
|
return out
|
|
229
229
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "proxy-aiops"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
description = "Governed AI-ops for Traefik + Caddy + HAProxy reverse proxies / load balancers: routes, services, upstream health, TLS cert expiry sweep, 5xx error-rate RCA, route conflict analysis, and governed writes (caddy config, haproxy server state/weight) with a built-in governance harness (audit, budget, undo, risk tiers)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
"url": "https://github.com/AIops-tools/Proxy-AIops",
|
|
8
8
|
"source": "github"
|
|
9
9
|
},
|
|
10
|
-
"version": "0.
|
|
10
|
+
"version": "0.3.0",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "pypi",
|
|
14
14
|
"identifier": "proxy-aiops",
|
|
15
|
-
"version": "0.
|
|
15
|
+
"version": "0.3.0",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|
|
18
18
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Integer quantities must stay exact integers.
|
|
2
|
+
|
|
3
|
+
Regression from live verification against Traefik 3.2: route priority is an
|
|
4
|
+
int64, and routing it through the float helper both rendered it in scientific
|
|
5
|
+
notation and lost precision — two routers with *different* priorities
|
|
6
|
+
(…806 and …805) both displayed as 9.223372036854776e+18. Route priority decides
|
|
7
|
+
matching order, so collapsing distinct values is actively misleading.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import pytest
|
|
13
|
+
|
|
14
|
+
from proxy_aiops.ops._util import as_int, num
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@pytest.mark.unit
|
|
18
|
+
def test_as_int_is_exact_for_large_int64_values():
|
|
19
|
+
hi = 9223372036854775806 # 2**63 - 2, as Traefik reports for api@internal
|
|
20
|
+
lo = 9223372036854775805
|
|
21
|
+
assert as_int(hi) == hi
|
|
22
|
+
assert as_int(lo) == lo
|
|
23
|
+
assert as_int(hi) != as_int(lo), "distinct priorities must not collapse"
|
|
24
|
+
# The float helper is exactly what could not do this:
|
|
25
|
+
assert num(hi) == num(lo), "documents why num() is wrong for priorities"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@pytest.mark.unit
|
|
29
|
+
def test_as_int_types_and_edges():
|
|
30
|
+
assert isinstance(as_int("42"), int) and as_int("42") == 42
|
|
31
|
+
assert as_int(3.9) == 3
|
|
32
|
+
assert as_int(None) == 0
|
|
33
|
+
assert as_int("nonsense") == 0
|
|
34
|
+
assert as_int(True) == 0, "a bool is not a quantity"
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
# Release notes — proxy-aiops 0.2.0
|
|
2
|
-
|
|
3
|
-
Previous release: 0.1.0.
|
|
4
|
-
|
|
5
|
-
## Headline: read-only mode
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
export PROXY_READ_ONLY=1
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
With this set the **6 write tools are never registered** — an MCP
|
|
12
|
-
client lists **22 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/proxy-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,191 +0,0 @@
|
|
|
1
|
-
# Live verification — Traefik / Caddy / HAProxy
|
|
2
|
-
|
|
3
|
-
`proxy-aiops` is exercised by a **mock-only** test suite (`uv run pytest`, no real
|
|
4
|
-
proxy). It has **not** yet been validated end-to-end against a live Traefik, Caddy, or
|
|
5
|
-
HAProxy instance. 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 proxy.
|
|
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 (`backend_health_rca`, `cert_expiry_sweep`,
|
|
18
|
-
`error_rate_rca`, `route_conflict_analysis`) are unit-tested against synthetic
|
|
19
|
-
telemetry: the health-check failure classes (connection refused / L4 timeout / TLS /
|
|
20
|
-
L7 / DNS / maint) each map to the right cause, the dominant-status-code mapping
|
|
21
|
-
(502/503/504/500) fires correctly, expiry bucketing respects the warn/critical
|
|
22
|
-
thresholds, and shadowed / dead / looping routes are each detected with the covering
|
|
23
|
-
route or missing service named.
|
|
24
|
-
- The **platform registry** resolves each tool to the correct Traefik (`/api/...` plus
|
|
25
|
-
the `/metrics` text format), Caddy (admin API), and HAProxy (Data Plane API v2
|
|
26
|
-
`/v2/...`, HTTP Basic) request shape.
|
|
27
|
-
- The **support matrix** is asserted to raise a *teaching error* — never a silent no-op —
|
|
28
|
-
for operations a platform cannot perform (Traefik writes → edit its providers; Caddy
|
|
29
|
-
error counters → access logs; HAProxy certs → the `.pem` pipeline).
|
|
30
|
-
- Reversible writes record a faithful **inverse** undo descriptor built from a fetched
|
|
31
|
-
before-state (`set_config_value` restores the prior subtree, or deletes a path it
|
|
32
|
-
created; `delete_config_path` re-creates the captured subtree; `set_server_state` /
|
|
33
|
-
`set_server_weight` restore the prior admin state / weight), and the descriptor's
|
|
34
|
-
params replay against the tool's own signature.
|
|
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 REST paths, JSON field names, and the
|
|
40
|
-
Traefik `/metrics` text format match a real build of each proxy. Those are modelled from
|
|
41
|
-
each project's public API documentation and are the **largest verification debt in this
|
|
42
|
-
repo** — the metrics-text parsing in particular is brittle against format changes.
|
|
43
|
-
|
|
44
|
-
## Prerequisites for a live run
|
|
45
|
-
|
|
46
|
-
All three proxies are free and self-hostable; a single compose file gets you the whole
|
|
47
|
-
matrix in an evening:
|
|
48
|
-
|
|
49
|
-
- **Traefik** — expose the API (`--api=true`, a router on `api@internal`) **and**
|
|
50
|
-
metrics (`--metrics.prometheus=true`), since `traffic_stats` / `error_counters` read
|
|
51
|
-
the metrics endpoint, not the API.
|
|
52
|
-
- **Caddy** — the admin API on `localhost:2019` (its default). This is the target that
|
|
53
|
-
carries the config write surface, so it is where sections 4–6 actually run.
|
|
54
|
-
- **HAProxy** — plus the **Data Plane API v2** (`dataplaneapi`), with a Basic-auth user.
|
|
55
|
-
This is the target that carries the runtime server writes.
|
|
56
|
-
|
|
57
|
-
Behind each proxy, put **at least two throwaway backends** so you can drain one, and a
|
|
58
|
-
deliberately broken one (a closed port, and a TLS-mismatched one) so the health RCA has
|
|
59
|
-
real failures to classify. Use a **lab edge** — the checklist drains servers and rewrites
|
|
60
|
-
config, and `load_config` replaces a Caddy instance's *entire* configuration.
|
|
61
|
-
|
|
62
|
-
```bash
|
|
63
|
-
uv tool install proxy-aiops
|
|
64
|
-
proxy-aiops init # wizard: pick platform, optional encrypted secret
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
Record the exact versions tested (e.g. "Traefik 3.2, Caddy 2.8, HAProxy 3.0 +
|
|
68
|
-
dataplaneapi 3.0") — a tick is only meaningful with the build it was ticked against.
|
|
69
|
-
|
|
70
|
-
## Verification checklist
|
|
71
|
-
|
|
72
|
-
Tick every box, **per platform**. A box that cannot be ticked is a verification gap —
|
|
73
|
-
record it, do not silently pass.
|
|
74
|
-
|
|
75
|
-
### 1. Connectivity (the fastest live gate)
|
|
76
|
-
- [ ] `proxy-aiops doctor` → green on each target: config parsed, secret store unlocks
|
|
77
|
-
(HAProxy), and a real health/info probe returns.
|
|
78
|
-
- [ ] `proxy-aiops doctor --skip-auth` → passes offline (config/secret checks only).
|
|
79
|
-
- [ ] One config spanning **all three** platforms works with `--target` switching — this
|
|
80
|
-
mixed-edge case is the reason the platform registry exists.
|
|
81
|
-
|
|
82
|
-
### 2. Reads return real, well-shaped data
|
|
83
|
-
- [ ] `proxy-aiops overview` → real platform/version, route and service counts, and
|
|
84
|
-
upstream up/down totals matching each proxy's own dashboard.
|
|
85
|
-
- [ ] `proxy-aiops routes list` → the real routers/routes/frontends with correct hosts,
|
|
86
|
-
paths, priority, and TLS flags.
|
|
87
|
-
- [ ] `proxy-aiops routes show <name>` → one route's full detail.
|
|
88
|
-
- [ ] `proxy-aiops routes find <host> --path /api` → returns the route that **actually**
|
|
89
|
-
serves that request. Verify by curling the host and comparing which backend
|
|
90
|
-
answers — this is the single most valuable correctness check in the file.
|
|
91
|
-
- [ ] `proxy-aiops services list` / `services show <name>` / `services upstreams <name>`
|
|
92
|
-
→ real servers with correct address, status (up/down/maint/drain), check info, and
|
|
93
|
-
weight.
|
|
94
|
-
- [ ] MCP `list_middlewares` → real Traefik middlewares; a **teaching error** on Caddy
|
|
95
|
-
and HAProxy rather than an empty list.
|
|
96
|
-
- [ ] MCP `traffic_stats` / `error_counters` → numbers that move when you generate load,
|
|
97
|
-
and that agree with the proxy's own stats page. On Traefik confirm the
|
|
98
|
-
**`/metrics` text parsing** survives the real format; on Caddy confirm the
|
|
99
|
-
teaching error (counters come from access logs there).
|
|
100
|
-
- [ ] `proxy-aiops config snapshot` / `config search <needle>` / `config get <path>` →
|
|
101
|
-
the real Caddy config tree; teaching errors on Traefik/HAProxy.
|
|
102
|
-
- [ ] `proxy-aiops certs` → the real TLS domain inventory; teaching note on HAProxy.
|
|
103
|
-
|
|
104
|
-
### 3. The analyses are right, not just non-crashing
|
|
105
|
-
- [ ] Stop one backend; `proxy-aiops analyze health` names that server and classifies the
|
|
106
|
-
failure as connection-refused (not a generic "down").
|
|
107
|
-
- [ ] Point a backend at a TLS-mismatched endpoint; the class comes back TLS, not L4.
|
|
108
|
-
- [ ] Drive real 503s (all servers of one service down) and real 504s (a backend that
|
|
109
|
-
sleeps past the timeout); `proxy-aiops analyze errors --rate 5 --min-requests 100`
|
|
110
|
-
flags the right service and maps each **dominant code to the correct cause**.
|
|
111
|
-
- [ ] Confirm `--min-requests` actually suppresses a low-traffic service with a single
|
|
112
|
-
failure.
|
|
113
|
-
- [ ] `proxy-aiops certs --sweep --warn-days 30 --critical-days 7` → live-probed expiry
|
|
114
|
-
dates match `openssl s_client` for the same domain, and bucketing is correct
|
|
115
|
-
either side of both thresholds. Repeat with `--port` on a non-443 listener.
|
|
116
|
-
- [ ] Create a genuinely shadowed route (a broad higher-priority route above a specific
|
|
117
|
-
one) and a dead route (pointing at a nonexistent service);
|
|
118
|
-
`proxy-aiops analyze conflicts` names the covering route and the missing service.
|
|
119
|
-
|
|
120
|
-
### 4. A reversible write + its undo — Caddy config
|
|
121
|
-
- [ ] `proxy-aiops config set <path> '<json>' --dry-run` → prints the write, changes
|
|
122
|
-
nothing (confirm with `config get <path>`).
|
|
123
|
-
- [ ] `proxy-aiops config set <path> '<json>'` → the config actually changes, the result
|
|
124
|
-
carries an `_undo_id`, and a row lands in `~/.proxy-aiops/audit.db`.
|
|
125
|
-
- [ ] `proxy-aiops undo list` shows it; `undo apply <id>` restores the **prior subtree
|
|
126
|
-
byte-for-byte** (compare against the `config snapshot` you took first).
|
|
127
|
-
- [ ] `config set` on a path that did **not** previously exist, then `undo apply` → the
|
|
128
|
-
created path is *deleted*, not reset to a guessed value. This asymmetric case is
|
|
129
|
-
where a naive undo silently leaves debris.
|
|
130
|
-
- [ ] MCP `delete_config_path` then `undo apply` → the deleted subtree is re-created
|
|
131
|
-
intact.
|
|
132
|
-
|
|
133
|
-
### 5. A reversible write + its undo — HAProxy runtime
|
|
134
|
-
- [ ] `proxy-aiops server state <backend> <server> drain --dry-run` → nothing changes.
|
|
135
|
-
- [ ] `proxy-aiops server state <backend> <server> drain` → the server actually drains
|
|
136
|
-
(confirm in the HAProxy stats page), in-flight connections finish, and an
|
|
137
|
-
`_undo_id` is returned.
|
|
138
|
-
- [ ] `undo apply <id>` restores the **prior** state — verify starting from a server that
|
|
139
|
-
was already in `maint`, where a naive undo would wrongly set `ready`.
|
|
140
|
-
- [ ] `proxy-aiops server weight <backend> <server> 50` then `undo apply` → the prior
|
|
141
|
-
weight returns, not a default.
|
|
142
|
-
- [ ] The same commands against a Traefik or Caddy target raise a **teaching error**
|
|
143
|
-
naming the right mechanism.
|
|
144
|
-
|
|
145
|
-
### 6. Governance actually gates
|
|
146
|
-
- [ ] With no `~/.proxy-aiops/rules.yaml`, `delete_config_path` and `load_config` are
|
|
147
|
-
**refused** unless `PROXY_AUDIT_APPROVED_BY` is set (secure-by-default); with it
|
|
148
|
-
plus `PROXY_AUDIT_RATIONALE`, both appear in the audit row.
|
|
149
|
-
- [ ] `load_config` on a lab Caddy replaces the whole config, and `undo apply` restores
|
|
150
|
-
the snapshotted original — then confirm traffic actually flows again, since a
|
|
151
|
-
restored-but-not-reloaded config would still be an outage.
|
|
152
|
-
- [ ] A tight poll loop trips the runaway budget guard rather than hammering the API.
|
|
153
|
-
- [ ] A failed call (nonexistent route name) is audited `status=error` with no undo.
|
|
154
|
-
|
|
155
|
-
### 7. Cleanup
|
|
156
|
-
- [ ] Return every drained server to its prior state, restore every weight, and undo
|
|
157
|
-
every config edit.
|
|
158
|
-
- [ ] `proxy-aiops overview` and `proxy-aiops config snapshot` match the baseline you
|
|
159
|
-
captured before starting.
|
|
160
|
-
- [ ] Skim `~/.proxy-aiops/audit.db` — every write is there with the right risk tier.
|
|
161
|
-
|
|
162
|
-
## Criteria to consider it live-verified
|
|
163
|
-
|
|
164
|
-
All of the following must hold:
|
|
165
|
-
|
|
166
|
-
1. Every box above is ticked against **all three** platforms, with the exact builds
|
|
167
|
-
recorded (e.g. "Traefik 3.2 + Caddy 2.8 + HAProxy 3.0/dataplaneapi 3.0").
|
|
168
|
-
2. Every REST-path, field-shape, or metrics-format mismatch found is **fixed and covered
|
|
169
|
-
by a regression test**, so the mock suite would now catch it.
|
|
170
|
-
3. Sections 4 and 5 passed, including the asymmetric cases (creating a config path then
|
|
171
|
-
undoing it; undoing a drain on a server that started in `maint`). Recording an undo
|
|
172
|
-
descriptor is not the same as the undo working, and this product line has shipped
|
|
173
|
-
broken undo pairs before.
|
|
174
|
-
4. Every support-matrix teaching error was confirmed **live** — a real unsupported call
|
|
175
|
-
must teach, not silently succeed or silently do nothing.
|
|
176
|
-
5. The run is written up in the release notes / product-line memory with the date and
|
|
177
|
-
package version, matching how the line records its other live-verified tools.
|
|
178
|
-
|
|
179
|
-
Until then, this repo says only what is true: mock-validated, live-unverified. Claiming
|
|
180
|
-
otherwise would break that promise.
|
|
181
|
-
|
|
182
|
-
## Notes for maintainers
|
|
183
|
-
|
|
184
|
-
- `proxy-aiops doctor` is the single fastest live entry point; start there.
|
|
185
|
-
- Weight the run toward **Traefik `/metrics` text parsing** and the **Caddy config-path
|
|
186
|
-
semantics** — those are the two places the mocks are least able to model, and both
|
|
187
|
-
change between releases.
|
|
188
|
-
- Managed cloud load balancers are explicitly out of scope; do not infer support for them
|
|
189
|
-
from a self-hosted run.
|
|
190
|
-
- Add this tool's result to the product-line verification ledger once green, so the
|
|
191
|
-
central "verification debt" list stays accurate.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|