postgres-aiops 0.1.1__tar.gz → 0.2.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.
- postgres_aiops-0.2.0/.coverage +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/CHANGELOG.md +24 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/PKG-INFO +1 -1
- postgres_aiops-0.2.0/postgres_aiops/__init__.py +14 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/postgres_aiops/cli/_common.py +1 -1
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/postgres_aiops/cli/init.py +38 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/postgres_aiops/cli/query.py +7 -4
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/postgres_aiops/governance/__init__.py +1 -1
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/postgres_aiops/governance/audit.py +1 -1
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/postgres_aiops/governance/budget.py +1 -1
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/postgres_aiops/governance/decorators.py +3 -3
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/postgres_aiops/governance/policy.py +24 -5
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/postgres_aiops/governance/sanitize.py +10 -4
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/postgres_aiops/ops/_util.py +1 -1
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/pyproject.toml +2 -1
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/server.json +2 -2
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/tests/conftest.py +9 -0
- postgres_aiops-0.2.0/tests/test_cli_writes.py +83 -0
- postgres_aiops-0.2.0/tests/test_governance_persistence.py +148 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/tests/test_smoke.py +9 -2
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/uv.lock +160 -1
- postgres_aiops-0.1.1/postgres_aiops/__init__.py +0 -9
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/.github/workflows/publish.yml +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/.gitignore +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/LICENSE +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/README.md +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/RELEASE_NOTES.md +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/SECURITY.md +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/mcp_server/__init__.py +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/mcp_server/_shared.py +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/mcp_server/server.py +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/mcp_server/tools/__init__.py +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/mcp_server/tools/activity.py +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/mcp_server/tools/analysis.py +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/mcp_server/tools/indexes.py +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/mcp_server/tools/queries.py +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/mcp_server/tools/remediation.py +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/mcp_server/tools/replication.py +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/mcp_server/tools/server.py +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/mcp_server/tools/tables.py +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/postgres_aiops/cli/__init__.py +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/postgres_aiops/cli/_root.py +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/postgres_aiops/cli/activity.py +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/postgres_aiops/cli/analyze.py +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/postgres_aiops/cli/doctor.py +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/postgres_aiops/cli/index.py +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/postgres_aiops/cli/overview.py +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/postgres_aiops/cli/remediate.py +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/postgres_aiops/cli/replication.py +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/postgres_aiops/cli/secret.py +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/postgres_aiops/cli/server.py +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/postgres_aiops/cli/table.py +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/postgres_aiops/config.py +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/postgres_aiops/connection.py +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/postgres_aiops/doctor.py +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/postgres_aiops/governance/paths.py +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/postgres_aiops/governance/patterns.py +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/postgres_aiops/governance/undo.py +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/postgres_aiops/ops/__init__.py +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/postgres_aiops/ops/activity.py +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/postgres_aiops/ops/analysis.py +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/postgres_aiops/ops/indexes.py +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/postgres_aiops/ops/overview.py +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/postgres_aiops/ops/queries.py +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/postgres_aiops/ops/remediation.py +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/postgres_aiops/ops/replication.py +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/postgres_aiops/ops/server.py +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/postgres_aiops/ops/tables.py +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/postgres_aiops/secretstore.py +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/skills/postgres-aiops/SKILL.md +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/skills/postgres-aiops/references/capabilities.md +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/skills/postgres-aiops/references/cli-reference.md +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/skills/postgres-aiops/references/setup-guide.md +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/smithery.yaml +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/tests/test_analysis.py +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/tests/test_connection.py +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/tests/test_reads.py +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/tests/test_secretstore.py +0 -0
- {postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/tests/test_writes.py +0 -0
|
Binary file
|
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v0.2.0 — 2026-07-13
|
|
4
|
+
|
|
5
|
+
Security-hardening release from a line-wide code review.
|
|
6
|
+
|
|
7
|
+
### Changed (behavior)
|
|
8
|
+
- **Secure by default**: with no `rules.yaml`, high/critical operations now require a
|
|
9
|
+
named approver (`POSTGRES_AUDIT_APPROVED_BY`). A fresh install no longer allows
|
|
10
|
+
destructive writes unattended; `init` seeds a starter `rules.yaml` you can edit,
|
|
11
|
+
and an operator-authored rules file is honoured as-is.
|
|
12
|
+
- `__version__` is now single-sourced from package metadata (the previous release
|
|
13
|
+
self-reported a stale version string).
|
|
14
|
+
- Sanitize docs no longer overstate scope: it strips control/format characters and
|
|
15
|
+
truncates; semantic prompt-injection resistance must come from the consuming agent.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
- CLI `query reset` now executes through the governed MCP twin — the last CLI write that bypassed audit/undo recording.
|
|
19
|
+
|
|
20
|
+
### Tests
|
|
21
|
+
- Governance persistence is now tested against REAL `audit.db`/`undo.db` files
|
|
22
|
+
(write → audit row + inverse undo row with captured prior state).
|
|
23
|
+
- The CLI confirmed-write path (dry-run / double-confirm / governed execution) is
|
|
24
|
+
covered end-to-end.
|
|
25
|
+
- `pytest-cov` added to the dev dependencies.
|
|
26
|
+
|
|
3
27
|
## v0.1.1
|
|
4
28
|
|
|
5
29
|
- Fix: `POSTGRES_AIOPS_HOME` now also relocates `config.yaml` (was hardcoded to `~/.postgres-aiops`).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: postgres-aiops
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Governed AI-ops for PostgreSQL DBA operations: slow-query RCA, bloat/vacuum & blocking-lock analysis 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,14 @@
|
|
|
1
|
+
"""postgres-aiops — governed PostgreSQL DBA operations for AI agents.
|
|
2
|
+
|
|
3
|
+
Standalone and self-contained: the governance harness (audit, token budget,
|
|
4
|
+
undo-token recording, graduated risk tiers, output sanitize) is bundled under
|
|
5
|
+
``postgres_aiops.governance`` — this package has no external skill-family
|
|
6
|
+
dependency. Preview: not yet full-coverage.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
__version__ = version("postgres-aiops")
|
|
13
|
+
except PackageNotFoundError: # running from an uninstalled source tree
|
|
14
|
+
__version__ = "0.0.0+unknown"
|
|
@@ -48,7 +48,7 @@ def cli_errors(fn: Callable) -> Callable:
|
|
|
48
48
|
return wrapper
|
|
49
49
|
|
|
50
50
|
|
|
51
|
-
def get_connection(target: str | None, config_path: Path | None = None):
|
|
51
|
+
def get_connection(target: str | None, config_path: Path | None = None) -> tuple[Any, Any]:
|
|
52
52
|
"""Return a (conn, config) tuple for the given target."""
|
|
53
53
|
from postgres_aiops.config import load_config
|
|
54
54
|
from postgres_aiops.connection import ConnectionManager
|
|
@@ -22,8 +22,45 @@ from postgres_aiops.config import (
|
|
|
22
22
|
DEFAULT_SSLMODE,
|
|
23
23
|
DEFAULT_USER,
|
|
24
24
|
)
|
|
25
|
+
from postgres_aiops.governance.paths import ops_path
|
|
25
26
|
from postgres_aiops.secretstore import SecretStore, resolve_master_password
|
|
26
27
|
|
|
28
|
+
# Starter policy: keeps the secure-by-default gate (high/critical writes need a
|
|
29
|
+
# named approver) explicit and editable, and shows the other rule kinds.
|
|
30
|
+
DEFAULT_RULES_YAML = """\
|
|
31
|
+
# postgres-aiops policy rules — hot-reloaded on change (no restart needed).
|
|
32
|
+
# Kinds: deny rules, maintenance_window, risk_tiers (graduated autonomy).
|
|
33
|
+
|
|
34
|
+
risk_tiers:
|
|
35
|
+
- name: high-risk-requires-approver
|
|
36
|
+
tier: dual
|
|
37
|
+
min_risk_level: high
|
|
38
|
+
reason: >-
|
|
39
|
+
High/critical writes need a named human approver — set
|
|
40
|
+
POSTGRES_AUDIT_APPROVED_BY (and POSTGRES_AUDIT_RATIONALE) before the call.
|
|
41
|
+
|
|
42
|
+
# deny:
|
|
43
|
+
# - name: no-prod-drops
|
|
44
|
+
# operations: ["drop_*"]
|
|
45
|
+
# environments: ["production"]
|
|
46
|
+
# reason: "Drops in production go through change management."
|
|
47
|
+
|
|
48
|
+
# maintenance_window:
|
|
49
|
+
# start: "22:00"
|
|
50
|
+
# end: "06:00"
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _write_default_rules() -> None:
|
|
55
|
+
"""Seed a starter rules.yaml (only when none exists) so the policy layer
|
|
56
|
+
is explicit from day one; never overwrites an operator-authored file."""
|
|
57
|
+
rules_path = ops_path("rules.yaml")
|
|
58
|
+
if rules_path.exists():
|
|
59
|
+
return
|
|
60
|
+
rules_path.parent.mkdir(parents=True, exist_ok=True)
|
|
61
|
+
rules_path.write_text(DEFAULT_RULES_YAML, "utf-8")
|
|
62
|
+
console.print(f"[green]✓ Wrote default policy rules:[/] {rules_path}")
|
|
63
|
+
|
|
27
64
|
|
|
28
65
|
def _load_existing_targets() -> list[dict]:
|
|
29
66
|
if not CONFIG_FILE.exists():
|
|
@@ -101,6 +138,7 @@ def init_cmd() -> None:
|
|
|
101
138
|
if not typer.confirm("\nAdd another target?", default=False):
|
|
102
139
|
break
|
|
103
140
|
|
|
141
|
+
_write_default_rules()
|
|
104
142
|
console.print(f"\n[green]✓ Setup complete.[/] Config: {CONFIG_FILE}")
|
|
105
143
|
console.print(
|
|
106
144
|
"[dim]Tip: export POSTGRES_AIOPS_MASTER_PASSWORD=... in your shell profile "
|
|
@@ -59,12 +59,15 @@ def query_explain(
|
|
|
59
59
|
@query_app.command("reset")
|
|
60
60
|
@cli_errors
|
|
61
61
|
def query_reset(target: TargetOption = None, dry_run: DryRunOption = False) -> None:
|
|
62
|
-
"""Reset pg_stat_statements accumulators (irreversible; dry-run + confirm).
|
|
63
|
-
|
|
62
|
+
"""Reset pg_stat_statements accumulators (irreversible; dry-run + confirm).
|
|
63
|
+
|
|
64
|
+
Real execution is delegated to the ``@governed_tool``-wrapped MCP function
|
|
65
|
+
so the reset is audited on the same governance path as MCP calls.
|
|
66
|
+
"""
|
|
67
|
+
from mcp_server.tools import queries as gov
|
|
64
68
|
|
|
65
69
|
if dry_run:
|
|
66
70
|
dry_run_print(operation="reset_query_stats", api_call="SELECT pg_stat_statements_reset()")
|
|
67
71
|
return
|
|
68
72
|
double_confirm("reset pg_stat_statements on", "this target")
|
|
69
|
-
|
|
70
|
-
console.print_json(json.dumps(ops.reset_query_stats(conn)))
|
|
73
|
+
console.print_json(json.dumps(gov.reset_query_stats(target=target)))
|
|
@@ -8,7 +8,7 @@ any external skill family — this package is its own copy of the harness:
|
|
|
8
8
|
and undo-token recording.
|
|
9
9
|
- unified SQLite audit log under ``~/.postgres-aiops/`` (override with
|
|
10
10
|
``POSTGRES_AIOPS_HOME``).
|
|
11
|
-
- ``sanitize`` —
|
|
11
|
+
- ``sanitize`` — control-character stripping + truncation for API-returned text.
|
|
12
12
|
|
|
13
13
|
State lives under ``ops_home()`` (default ``~/.postgres-aiops``).
|
|
14
14
|
"""
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Unified audit logging engine — all
|
|
1
|
+
"""Unified audit logging engine — all PostgreSQL skills write to a single SQLite database.
|
|
2
2
|
|
|
3
3
|
Replaces 7 per-skill JSON Lines audit loggers with one shared ``~/.postgres-aiops/audit.db``.
|
|
4
4
|
Framework-agnostic: works with Claude, Codex, local agents, or any MCP client.
|
|
@@ -43,7 +43,7 @@ _DEFAULT_RUNAWAY_WINDOW_SEC = 120
|
|
|
43
43
|
class BudgetExceeded(Exception):
|
|
44
44
|
"""Raised when a tool call would exceed a budget / runaway limit.
|
|
45
45
|
|
|
46
|
-
Subclasses nothing
|
|
46
|
+
Subclasses nothing PostgreSQL-specific; the decorator wraps it into a
|
|
47
47
|
PolicyResult so it shares the existing denial audit path. Kept as its own
|
|
48
48
|
type (not raw PolicyDenied) so callers can catch budget stops distinctly.
|
|
49
49
|
"""
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""The ``@governed_tool`` decorator — mandatory wrapper for all
|
|
1
|
+
"""The ``@governed_tool`` decorator — mandatory wrapper for all PostgreSQL MCP tool functions.
|
|
2
2
|
|
|
3
3
|
Responsibilities:
|
|
4
4
|
1. Pre-check: evaluate policy rules (deny, maintenance window)
|
|
@@ -58,7 +58,7 @@ def governed_tool(
|
|
|
58
58
|
sensitive_params: list[str] | None = None,
|
|
59
59
|
undo: Any = None,
|
|
60
60
|
) -> Any:
|
|
61
|
-
"""Decorator for all
|
|
61
|
+
"""Decorator for all PostgreSQL MCP tool functions.
|
|
62
62
|
|
|
63
63
|
Can be used with or without arguments::
|
|
64
64
|
|
|
@@ -365,7 +365,7 @@ def _finalize(state: _CallState) -> None:
|
|
|
365
365
|
pass
|
|
366
366
|
|
|
367
367
|
# timeout_seconds is advisory: exceeding it logs a warning, no hard
|
|
368
|
-
# cancellation (cancelling mid-flight
|
|
368
|
+
# cancellation (cancelling mid-flight PostgreSQL calls is worse).
|
|
369
369
|
if state.timeout_seconds and duration > state.timeout_seconds * 1000:
|
|
370
370
|
_log.warning(
|
|
371
371
|
"%s.%s took %dms — exceeded timeout_seconds=%d (advisory, not cancelled)",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Policy engine — rule-based access control for
|
|
1
|
+
"""Policy engine — rule-based access control for PostgreSQL MCP tools.
|
|
2
2
|
|
|
3
3
|
Rules are loaded from ``~/.postgres-aiops/rules.yaml`` with hot-reload on file change.
|
|
4
4
|
"""
|
|
@@ -93,7 +93,8 @@ class PolicyEngine:
|
|
|
93
93
|
self._load_rules()
|
|
94
94
|
|
|
95
95
|
def _load_rules(self) -> None:
|
|
96
|
-
"""Load rules from YAML file.
|
|
96
|
+
"""Load rules from YAML file. Missing file → empty rules (deny rules off;
|
|
97
|
+
high/critical still require an approver — see required_approval_tier)."""
|
|
97
98
|
if not self._path.exists():
|
|
98
99
|
self._rules = {}
|
|
99
100
|
self._mtime = 0.0
|
|
@@ -113,7 +114,7 @@ class PolicyEngine:
|
|
|
113
114
|
"""Hot-reload if file changed."""
|
|
114
115
|
if not self._path.exists():
|
|
115
116
|
if self._rules:
|
|
116
|
-
_log.warning("Policy rules file deleted: %s — clearing rules (
|
|
117
|
+
_log.warning("Policy rules file deleted: %s — clearing rules (high/critical fall back to the default approver gate)", self._path)
|
|
117
118
|
self._rules = {}
|
|
118
119
|
self._mtime = 0.0
|
|
119
120
|
return
|
|
@@ -156,7 +157,8 @@ class PolicyEngine:
|
|
|
156
157
|
|
|
157
158
|
self._maybe_reload()
|
|
158
159
|
|
|
159
|
-
# No rules file →
|
|
160
|
+
# No rules file → no deny rules (destructive ops are still gated by
|
|
161
|
+
# required_approval_tier's secure-by-default dual tier)
|
|
160
162
|
if not self._rules:
|
|
161
163
|
return PolicyResult(allowed=True, rule="no_rules")
|
|
162
164
|
|
|
@@ -225,11 +227,28 @@ class PolicyEngine:
|
|
|
225
227
|
on operation glob / environment / resource tag / minimum risk and maps
|
|
226
228
|
to a tier (none/confirm/dual/review). The FIRST matching, HIGHEST tier
|
|
227
229
|
wins so a prod-tagged destructive op can't be down-graded by a looser
|
|
228
|
-
rule listed earlier.
|
|
230
|
+
rule listed earlier.
|
|
231
|
+
|
|
232
|
+
Secure by default: with NO rules file at all, high/critical operations
|
|
233
|
+
require a named approver (``dual`` tier) — a fresh install must not
|
|
234
|
+
allow destructive writes unattended. An operator-authored rules file
|
|
235
|
+
(even one without a ``risk_tiers`` block) is an explicit choice and is
|
|
236
|
+
honoured as-is (tier ``none``).
|
|
229
237
|
"""
|
|
238
|
+
if os.environ.get("POSTGRES_POLICY_DISABLED") == "1":
|
|
239
|
+
return TierDecision(tier="none", rule="policy_disabled")
|
|
230
240
|
self._maybe_reload()
|
|
231
241
|
tiers = self._rules.get("risk_tiers") if self._rules else None
|
|
232
242
|
if not tiers:
|
|
243
|
+
if not self._rules and risk_level in ("high", "critical"):
|
|
244
|
+
return TierDecision(
|
|
245
|
+
tier="dual",
|
|
246
|
+
rule="default_high_risk",
|
|
247
|
+
reason=(
|
|
248
|
+
f"No rules file at {self._path} — high/critical operations require a named approver (POSTGRES_AUDIT_APPROVED_BY) by default. Author rules.yaml "
|
|
249
|
+
"(run the init wizard) to customize this."
|
|
250
|
+
),
|
|
251
|
+
)
|
|
233
252
|
return TierDecision(tier="none", rule="no_tiers")
|
|
234
253
|
|
|
235
254
|
tags = _extract_tags(params)
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Output hygiene: strip control/format characters and truncate untrusted text.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Defense-in-depth against *encoding-level* tricks: control characters,
|
|
4
|
+
zero-width / bidi format characters, and payload-pushing padding. It does NOT
|
|
5
|
+
neutralize natural-language prompt injection — "ignore previous instructions"
|
|
6
|
+
passes through unchanged; semantic injection resistance must come from the
|
|
7
|
+
consuming agent's own prompt boundaries.
|
|
8
|
+
|
|
9
|
+
Consolidated from 22 duplicate ``_sanitize()`` implementations across the tool line.
|
|
4
10
|
All skills should import from here instead of defining their own copy.
|
|
5
11
|
"""
|
|
6
12
|
|
|
@@ -19,14 +25,14 @@ def sanitize(text: str | None, max_len: int = 500) -> str:
|
|
|
19
25
|
Removes:
|
|
20
26
|
- C0/C1 control characters (except newline/tab)
|
|
21
27
|
- Unicode Format characters (Cf): zero-width spaces, bidi overrides,
|
|
22
|
-
zero-width joiners — used
|
|
28
|
+
zero-width joiners — used to smuggle or disguise injected text
|
|
23
29
|
|
|
24
30
|
Stripping happens BEFORE truncation so an attacker cannot push the real
|
|
25
31
|
payload past the cut-off by padding with junk control characters.
|
|
26
32
|
``None`` sanitizes to ``""``.
|
|
27
33
|
|
|
28
34
|
Args:
|
|
29
|
-
text: Untrusted text from
|
|
35
|
+
text: Untrusted text from PostgreSQL server responses.
|
|
30
36
|
max_len: Maximum length after truncation. Default 500.
|
|
31
37
|
|
|
32
38
|
Returns:
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Two jobs:
|
|
4
4
|
|
|
5
5
|
* ``s`` — pass catalog/query text through the governance ``sanitize`` (bounded
|
|
6
|
-
length,
|
|
6
|
+
length, control-character stripping) before it reaches an agent.
|
|
7
7
|
* ``qualify`` / ``quote_ident`` — the ONLY sanctioned way to place an
|
|
8
8
|
identifier (schema/table/index/column) into a statement that cannot be
|
|
9
9
|
parameterised (DDL, ``VACUUM``, ``ANALYZE``, ``REINDEX``). Every part is
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "postgres-aiops"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.0"
|
|
8
8
|
description = "Governed AI-ops for PostgreSQL DBA operations: slow-query RCA, bloat/vacuum & blocking-lock analysis with a built-in governance harness (audit, budget, undo, risk tiers)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -22,6 +22,7 @@ dependencies = [
|
|
|
22
22
|
[dependency-groups]
|
|
23
23
|
dev = [
|
|
24
24
|
"pytest>=8.0,<10.0",
|
|
25
|
+
"pytest-cov>=5.0,<8.0",
|
|
25
26
|
"ruff>=0.5,<1.0",
|
|
26
27
|
]
|
|
27
28
|
|
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
"url": "https://github.com/AIops-tools/Postgres-AIops",
|
|
8
8
|
"source": "github"
|
|
9
9
|
},
|
|
10
|
-
"version": "0.
|
|
10
|
+
"version": "0.2.0",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "pypi",
|
|
14
14
|
"identifier": "postgres-aiops",
|
|
15
|
-
"version": "0.
|
|
15
|
+
"version": "0.2.0",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|
|
18
18
|
}
|
|
@@ -13,6 +13,15 @@ from typing import Any
|
|
|
13
13
|
import pytest
|
|
14
14
|
|
|
15
15
|
|
|
16
|
+
@pytest.fixture(autouse=True)
|
|
17
|
+
def _default_approver(monkeypatch):
|
|
18
|
+
"""The policy layer is secure-by-default: with no rules.yaml, high/critical
|
|
19
|
+
governed calls require a named approver. Tests exercising tool behavior
|
|
20
|
+
are not about that gate, so record a synthetic approver globally; the
|
|
21
|
+
governance-persistence tests remove it to test the gate itself."""
|
|
22
|
+
monkeypatch.setenv("POSTGRES_AUDIT_APPROVED_BY", "pytest")
|
|
23
|
+
|
|
24
|
+
|
|
16
25
|
class FakePg:
|
|
17
26
|
def __init__(
|
|
18
27
|
self,
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""CLI confirmed-write path — past dry-run, through governance, onto disk.
|
|
2
|
+
|
|
3
|
+
The CLI write commands delegate real execution to the ``@governed_tool``
|
|
4
|
+
functions in ``mcp_server.tools``. These tests drive a write command PAST the
|
|
5
|
+
dry-run branch and the double-confirm prompts and assert the call really went
|
|
6
|
+
through the governed path (audit row on disk) — the regression test for the
|
|
7
|
+
"CLI writes were unaudited" line-wide fix.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import sqlite3
|
|
13
|
+
|
|
14
|
+
import pytest
|
|
15
|
+
from typer.testing import CliRunner
|
|
16
|
+
|
|
17
|
+
import postgres_aiops.governance.audit as audit_mod
|
|
18
|
+
import postgres_aiops.governance.policy as policy_mod
|
|
19
|
+
import postgres_aiops.governance.undo as undo_mod
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@pytest.fixture
|
|
23
|
+
def gov_home(tmp_path, monkeypatch):
|
|
24
|
+
monkeypatch.setenv("POSTGRES_AIOPS_HOME", str(tmp_path))
|
|
25
|
+
audit_mod.reset_engine()
|
|
26
|
+
policy_mod.reset_policy_engine()
|
|
27
|
+
undo_mod.reset_undo_store()
|
|
28
|
+
yield tmp_path
|
|
29
|
+
audit_mod.reset_engine()
|
|
30
|
+
policy_mod.reset_policy_engine()
|
|
31
|
+
undo_mod.reset_undo_store()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _audit_tools(db_path) -> list[str]:
|
|
35
|
+
conn = sqlite3.connect(db_path)
|
|
36
|
+
try:
|
|
37
|
+
return [r[0] for r in conn.execute("SELECT tool FROM audit_log ORDER BY id")]
|
|
38
|
+
finally:
|
|
39
|
+
conn.close()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@pytest.mark.unit
|
|
43
|
+
def test_cli_query_reset_dry_run_makes_no_call_and_no_audit(gov_home, monkeypatch, fake_pg):
|
|
44
|
+
from postgres_aiops.cli import app
|
|
45
|
+
|
|
46
|
+
fake = fake_pg()
|
|
47
|
+
import mcp_server.tools.queries as gov_queries
|
|
48
|
+
|
|
49
|
+
monkeypatch.setattr(gov_queries, "_get_connection", lambda target=None: fake)
|
|
50
|
+
result = CliRunner().invoke(app, ["query", "reset", "--dry-run"])
|
|
51
|
+
assert result.exit_code == 0
|
|
52
|
+
assert "DRY-RUN" in result.output
|
|
53
|
+
assert fake.executed == [] and fake.queried == []
|
|
54
|
+
assert not (gov_home / "audit.db").exists()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@pytest.mark.unit
|
|
58
|
+
def test_cli_query_reset_confirmed_goes_through_governance(gov_home, monkeypatch, fake_pg):
|
|
59
|
+
"""Confirmed CLI write must execute via the governed twin: the SQL runs
|
|
60
|
+
AND an audit row lands in audit.db (this is what the reroute fix bought)."""
|
|
61
|
+
from postgres_aiops.cli import app
|
|
62
|
+
|
|
63
|
+
fake = fake_pg(responses={"pg_stat_statements_reset": [{"reset": True}]})
|
|
64
|
+
import mcp_server.tools.queries as gov_queries
|
|
65
|
+
|
|
66
|
+
monkeypatch.setattr(gov_queries, "_get_connection", lambda target=None: fake)
|
|
67
|
+
result = CliRunner().invoke(app, ["query", "reset"], input="y\ny\n")
|
|
68
|
+
assert result.exit_code == 0, result.output
|
|
69
|
+
assert _audit_tools(gov_home / "audit.db") == ["reset_query_stats"]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@pytest.mark.unit
|
|
73
|
+
def test_cli_query_reset_aborts_without_double_confirm(gov_home, monkeypatch, fake_pg):
|
|
74
|
+
from postgres_aiops.cli import app
|
|
75
|
+
|
|
76
|
+
fake = fake_pg()
|
|
77
|
+
import mcp_server.tools.queries as gov_queries
|
|
78
|
+
|
|
79
|
+
monkeypatch.setattr(gov_queries, "_get_connection", lambda target=None: fake)
|
|
80
|
+
result = CliRunner().invoke(app, ["query", "reset"], input="y\nn\n")
|
|
81
|
+
assert result.exit_code != 0
|
|
82
|
+
assert fake.executed == [] and fake.queried == []
|
|
83
|
+
assert not (gov_home / "audit.db").exists()
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""Governance persistence — REAL audit.db / undo.db rows, not mocked stores.
|
|
2
|
+
|
|
3
|
+
The other governance tests monkeypatch the stores and only verify that undo
|
|
4
|
+
descriptors are *constructed*. These tests bind the whole harness to a
|
|
5
|
+
throwaway home (``POSTGRES_AIOPS_HOME``) and assert that the rows compliance
|
|
6
|
+
evidence is built from actually land on disk, and that the secure-by-default
|
|
7
|
+
approver gate (no rules.yaml → high/critical needs an approver) enforces.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import sqlite3
|
|
14
|
+
|
|
15
|
+
import pytest
|
|
16
|
+
|
|
17
|
+
import postgres_aiops.governance.audit as audit_mod
|
|
18
|
+
import postgres_aiops.governance.policy as policy_mod
|
|
19
|
+
import postgres_aiops.governance.undo as undo_mod
|
|
20
|
+
from postgres_aiops.governance import PolicyDenied, governed_tool
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _reset_singletons() -> None:
|
|
24
|
+
audit_mod.reset_engine()
|
|
25
|
+
policy_mod.reset_policy_engine()
|
|
26
|
+
undo_mod.reset_undo_store()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@pytest.fixture
|
|
30
|
+
def gov_home(tmp_path, monkeypatch):
|
|
31
|
+
"""Bind the harness to a temp home with NO approver and NO rules file."""
|
|
32
|
+
monkeypatch.setenv("POSTGRES_AIOPS_HOME", str(tmp_path))
|
|
33
|
+
monkeypatch.delenv("POSTGRES_AUDIT_APPROVED_BY", raising=False)
|
|
34
|
+
monkeypatch.delenv("POSTGRES_POLICY_DISABLED", raising=False)
|
|
35
|
+
_reset_singletons()
|
|
36
|
+
yield tmp_path
|
|
37
|
+
_reset_singletons()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _rows(db_path, table: str) -> list[dict]:
|
|
41
|
+
conn = sqlite3.connect(db_path)
|
|
42
|
+
conn.row_factory = sqlite3.Row
|
|
43
|
+
try:
|
|
44
|
+
return [dict(r) for r in conn.execute(f"SELECT * FROM {table}")] # noqa: S608
|
|
45
|
+
finally:
|
|
46
|
+
conn.close()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# Synthetic governed tools — they exercise the harness itself, so the tests
|
|
50
|
+
# stay valid even as the product tool surface evolves.
|
|
51
|
+
@governed_tool(
|
|
52
|
+
risk_level="medium",
|
|
53
|
+
undo=lambda p, r: {
|
|
54
|
+
"tool": "restore_widget",
|
|
55
|
+
"params": {"name": p["name"], "prior": r["priorState"]},
|
|
56
|
+
},
|
|
57
|
+
)
|
|
58
|
+
def _rename_widget(name: str, target: str = "") -> dict:
|
|
59
|
+
return {"status": "renamed", "priorState": f"old-{name}"}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@governed_tool(risk_level="high")
|
|
63
|
+
def _drop_widget(name: str, target: str = "") -> dict:
|
|
64
|
+
return {"status": "dropped"}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@pytest.mark.unit
|
|
68
|
+
def test_medium_write_persists_audit_and_undo_rows(gov_home):
|
|
69
|
+
result = _rename_widget(name="w1", target="prod-db")
|
|
70
|
+
assert result["status"] == "renamed"
|
|
71
|
+
assert result.get("_undo_id"), "successful write must carry an _undo_id"
|
|
72
|
+
|
|
73
|
+
audit = _rows(gov_home / "audit.db", "audit_log")
|
|
74
|
+
assert len(audit) == 1
|
|
75
|
+
row = audit[0]
|
|
76
|
+
assert row["tool"] == "_rename_widget"
|
|
77
|
+
assert row["status"] == "ok"
|
|
78
|
+
assert row["risk_level"] == "medium"
|
|
79
|
+
assert json.loads(row["params"])["name"] == "w1"
|
|
80
|
+
|
|
81
|
+
undo = _rows(gov_home / "undo.db", "undo_log")
|
|
82
|
+
assert len(undo) == 1
|
|
83
|
+
assert undo[0]["undo_id"] == result["_undo_id"]
|
|
84
|
+
assert undo[0]["undo_tool"] == "restore_widget"
|
|
85
|
+
assert json.loads(undo[0]["undo_params"]) == {"name": "w1", "prior": "old-w1"}
|
|
86
|
+
assert undo[0]["status"] == "recorded"
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@pytest.mark.unit
|
|
90
|
+
def test_high_risk_denied_without_approver_and_denial_is_audited(gov_home):
|
|
91
|
+
"""Secure by default: no rules.yaml + no approver → high risk is denied,
|
|
92
|
+
and the denial itself must land in the audit log."""
|
|
93
|
+
with pytest.raises(PolicyDenied, match="requires 'dual' approval"):
|
|
94
|
+
_drop_widget(name="w2", target="prod-db")
|
|
95
|
+
|
|
96
|
+
audit = _rows(gov_home / "audit.db", "audit_log")
|
|
97
|
+
assert len(audit) == 1
|
|
98
|
+
assert audit[0]["tool"] == "_drop_widget"
|
|
99
|
+
assert audit[0]["status"] == "denied"
|
|
100
|
+
assert audit[0]["risk_tier"] == "dual"
|
|
101
|
+
|
|
102
|
+
assert not (gov_home / "undo.db").exists() or not _rows(gov_home / "undo.db", "undo_log")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@pytest.mark.unit
|
|
106
|
+
def test_high_risk_allowed_with_named_approver(gov_home, monkeypatch):
|
|
107
|
+
monkeypatch.setenv("POSTGRES_AUDIT_APPROVED_BY", "dba-alice")
|
|
108
|
+
result = _drop_widget(name="w3", target="prod-db")
|
|
109
|
+
assert result["status"] == "dropped"
|
|
110
|
+
|
|
111
|
+
audit = _rows(gov_home / "audit.db", "audit_log")
|
|
112
|
+
assert len(audit) == 1
|
|
113
|
+
assert audit[0]["status"] == "ok"
|
|
114
|
+
assert audit[0]["approved_by"] == "dba-alice"
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@pytest.mark.unit
|
|
118
|
+
def test_operator_rules_file_restores_tier_none_for_high_risk(gov_home):
|
|
119
|
+
"""An operator-authored rules.yaml (without risk_tiers) is an explicit
|
|
120
|
+
choice: the default dual gate must stand down."""
|
|
121
|
+
(gov_home / "rules.yaml").write_text("deny: []\n", "utf-8")
|
|
122
|
+
_reset_singletons()
|
|
123
|
+
result = _drop_widget(name="w4", target="prod-db")
|
|
124
|
+
assert result["status"] == "dropped"
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@pytest.mark.unit
|
|
128
|
+
def test_real_write_tool_persists_priorstate_undo(gov_home, monkeypatch, fake_pg):
|
|
129
|
+
"""End-to-end through a REAL product write tool: drop_index must capture
|
|
130
|
+
pg_get_indexdef before dropping and persist the recreate-undo on disk."""
|
|
131
|
+
monkeypatch.setenv("POSTGRES_AUDIT_APPROVED_BY", "dba-alice")
|
|
132
|
+
indexdef = "CREATE INDEX idx_orders_ts ON public.orders USING btree (ts)"
|
|
133
|
+
fake = fake_pg(scalars={"pg_get_indexdef": indexdef})
|
|
134
|
+
|
|
135
|
+
from mcp_server.tools import remediation as gov
|
|
136
|
+
|
|
137
|
+
monkeypatch.setattr(gov, "_get_connection", lambda target=None: fake)
|
|
138
|
+
result = gov.drop_index(name="idx_orders_ts")
|
|
139
|
+
assert result["priorState"]["indexdef"] == indexdef
|
|
140
|
+
assert result.get("_undo_id")
|
|
141
|
+
|
|
142
|
+
undo = _rows(gov_home / "undo.db", "undo_log")
|
|
143
|
+
assert len(undo) == 1
|
|
144
|
+
assert indexdef in undo[0]["undo_params"]
|
|
145
|
+
|
|
146
|
+
audit = _rows(gov_home / "audit.db", "audit_log")
|
|
147
|
+
assert [r["tool"] for r in audit] == ["drop_index"]
|
|
148
|
+
assert audit[0]["risk_level"] == "high"
|
|
@@ -71,10 +71,17 @@ def test_all_modules_import():
|
|
|
71
71
|
|
|
72
72
|
|
|
73
73
|
@pytest.mark.unit
|
|
74
|
-
def
|
|
74
|
+
def test_version_matches_pyproject():
|
|
75
|
+
"""__version__ is single-sourced from package metadata; it must track
|
|
76
|
+
pyproject.toml so a release bump can never ship a stale self-report."""
|
|
77
|
+
import tomllib
|
|
78
|
+
from pathlib import Path
|
|
79
|
+
|
|
75
80
|
import postgres_aiops
|
|
76
81
|
|
|
77
|
-
|
|
82
|
+
pyproject = Path(__file__).resolve().parents[1] / "pyproject.toml"
|
|
83
|
+
expected = tomllib.loads(pyproject.read_text("utf-8"))["project"]["version"]
|
|
84
|
+
assert postgres_aiops.__version__ == expected
|
|
78
85
|
|
|
79
86
|
|
|
80
87
|
@pytest.mark.unit
|
|
@@ -176,6 +176,95 @@ wheels = [
|
|
|
176
176
|
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
|
177
177
|
]
|
|
178
178
|
|
|
179
|
+
[[package]]
|
|
180
|
+
name = "coverage"
|
|
181
|
+
version = "7.15.1"
|
|
182
|
+
source = { registry = "https://pypi.org/simple" }
|
|
183
|
+
sdist = { url = "https://files.pythonhosted.org/packages/4a/83/6051c2a2feab48ae5bd27c84ef047191d2d4a3172f689e38eaa48ed17db1/coverage-7.15.1.tar.gz", hash = "sha256:165e9949eaf222ef1f018635d0d7f368a23bfe0212af558534c40d8c04686d67", size = 927640, upload-time = "2026-07-12T20:58:19.908Z" }
|
|
184
|
+
wheels = [
|
|
185
|
+
{ url = "https://files.pythonhosted.org/packages/67/ee/5095e07a0986930174fc153fd0bb115f7f2724f5344cc672e016d4f23a4e/coverage-7.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6506330b4a8dcf53b95bd84d8d0e817107cdb3fc1438e835029cdf0bc6612eb0", size = 221320, upload-time = "2026-07-12T20:56:16.036Z" },
|
|
186
|
+
{ url = "https://files.pythonhosted.org/packages/43/7a/7e2598ce98433a214020a61c0755d5961f9f26df0c630dc73e06b86d3416/coverage-7.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8a51a8ec382c39d939cba0ab07ae949077ae4e842343bd4eed22d432358cea9", size = 221823, upload-time = "2026-07-12T20:56:17.54Z" },
|
|
187
|
+
{ url = "https://files.pythonhosted.org/packages/8b/4f/67dac6a69139b490a239d91b6d5ef127982ffb89159769241656ab879ee5/coverage-7.15.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:18ea20e3922d7f8ca9e0ef1084408d08c4ad62d5e531cb9c1f6896a99297ebea", size = 252242, upload-time = "2026-07-12T20:56:18.868Z" },
|
|
188
|
+
{ url = "https://files.pythonhosted.org/packages/05/37/5e439725a96de592309725550c8df639c359c209dcb4b152ed46032d4c0d/coverage-7.15.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:aab9902a64b8390e3b56e539fddae1d79a267807fe5cb0c18d7d2f544ce867e2", size = 254153, upload-time = "2026-07-12T20:56:20.118Z" },
|
|
189
|
+
{ url = "https://files.pythonhosted.org/packages/0f/1c/671ba9e15f9651a99962fb588a1fe4fb267cae4bb85f9bb4ac4413c6f7ef/coverage-7.15.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cc316264317b07a9e90d7f2b4188a15e36e9b54e651081b791b0515fa612a29", size = 256261, upload-time = "2026-07-12T20:56:21.682Z" },
|
|
190
|
+
{ url = "https://files.pythonhosted.org/packages/d6/3c/88305322b4d015b4536b7174b8f9b87f850f01af9d89008cdbf984b65ff2/coverage-7.15.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d4e47e7eea81a8ccf060a07627654151d929da62c7b715738387c200905cec89", size = 258222, upload-time = "2026-07-12T20:56:22.962Z" },
|
|
191
|
+
{ url = "https://files.pythonhosted.org/packages/8d/20/d02e42333a57f266ded61ed4fb3821da1f2dc143734aaa3dcc9c117204c5/coverage-7.15.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c6829d9a3b55ad2b73ef5fda8302e5be03683789e88b1a079dcf4a773229c21d", size = 252368, upload-time = "2026-07-12T20:56:24.475Z" },
|
|
192
|
+
{ url = "https://files.pythonhosted.org/packages/f3/d0/94ebc010926a191d83e83b1f1236f8bd0d14d0eb98b5c324aa4be2589a8e/coverage-7.15.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:764e045811f9c8cda436641f3f088283351d331a519b5807f19041cd0a68da1c", size = 253953, upload-time = "2026-07-12T20:56:26.036Z" },
|
|
193
|
+
{ url = "https://files.pythonhosted.org/packages/2e/a6/2c8553191da0c2da2c43ff294fb9bab57a5b0fae03560304a82a8b5addc3/coverage-7.15.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:09b3b088aa24489c4082bcc35fcc8224281ab94a653dfb6d3f0c8165b0d628ab", size = 252016, upload-time = "2026-07-12T20:56:27.374Z" },
|
|
194
|
+
{ url = "https://files.pythonhosted.org/packages/17/99/20f47986d8360fa1a31d9858dd2ba0be009d44ecfa8d02120b1eb93c028b/coverage-7.15.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7911b02f57053adf8164ae63edb1c26574d24dfccabadc5268cf69310a69a358", size = 255783, upload-time = "2026-07-12T20:56:28.921Z" },
|
|
195
|
+
{ url = "https://files.pythonhosted.org/packages/17/36/fb61983b5259f78fa5e62ee74e91fe4de4c556fcbc9a3b9c9021c2f8b57b/coverage-7.15.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:664e279ed40599b8ed16f4db18d92a7e212c73129672bec8f5d96d4da48d2404", size = 251735, upload-time = "2026-07-12T20:56:30.465Z" },
|
|
196
|
+
{ url = "https://files.pythonhosted.org/packages/6c/2e/d109d8d2d6c726ba2e78125297073918a2a91464f0ea777e5398fa3cf75c/coverage-7.15.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:34fe7cf79d5f1f87f2e8ce7dc1c32950841f50e10d0120f263856acfad66de34", size = 252645, upload-time = "2026-07-12T20:56:32.076Z" },
|
|
197
|
+
{ url = "https://files.pythonhosted.org/packages/bc/a0/5b3219cbb46135e14673427f2bf5890c4da4e6f655243692f3448aa3f42c/coverage-7.15.1-cp311-cp311-win32.whl", hash = "sha256:5e2d2536d2f57a354aa382ed303ac0e2e5c9522a508c05b998d26181b94163a7", size = 223414, upload-time = "2026-07-12T20:56:33.432Z" },
|
|
198
|
+
{ url = "https://files.pythonhosted.org/packages/5b/80/24e13ade0b6df8090e2492f9c55044bf1423f7ef28c76fc1af8c827a61cb/coverage-7.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:c337da8fca7ea93ab43f3868cfcde6cf6dad32c3906b273cfbad5d7390bc423b", size = 223888, upload-time = "2026-07-12T20:56:34.784Z" },
|
|
199
|
+
{ url = "https://files.pythonhosted.org/packages/2d/9e/34659930ae380491af48e2f5f5f9a2e99cd9fb7de3d30f27be5ac5425137/coverage-7.15.1-cp311-cp311-win_arm64.whl", hash = "sha256:db3403fdb7a94d5eb73e099befad8104d2a7d110a0f0d99df0de61c5d1fa756c", size = 223435, upload-time = "2026-07-12T20:56:36.302Z" },
|
|
200
|
+
{ url = "https://files.pythonhosted.org/packages/d9/76/32c1826309beaf4604c54accef108fdd611e5e5e93f2f5192f050cd5f6bd/coverage-7.15.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d9476292594309db922cc841dd13b303b3c388f4c25d279884f7e2341c681f80", size = 221497, upload-time = "2026-07-12T20:56:37.628Z" },
|
|
201
|
+
{ url = "https://files.pythonhosted.org/packages/db/5c/b88ce0d68fa550c7f3b58617fbf363bce64df5bf8295a01b627e4696e022/coverage-7.15.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0c579056b0de461b3a62318b63d0b6ce90aed7f8158d3f00da094df82f29d189", size = 221854, upload-time = "2026-07-12T20:56:39.033Z" },
|
|
202
|
+
{ url = "https://files.pythonhosted.org/packages/0e/fe/8509fd2a66fc4e0a829f76a0f0b1dc3cc163368352435b5f243168658077/coverage-7.15.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:23214bdbe226f2b0e9c66a7d6a1d59d4a88045dcf86e702cf0fe0d0935e3d615", size = 253359, upload-time = "2026-07-12T20:56:40.423Z" },
|
|
203
|
+
{ url = "https://files.pythonhosted.org/packages/e5/81/c7009ed7ea9765adb2b9d095054d748266fae5f07ac6c5f925f33715fcde/coverage-7.15.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:df164be93b46b4825cc39339440a05edc54c4d1d865ba4a60fd43d151a2a1cd3", size = 256096, upload-time = "2026-07-12T20:56:42.115Z" },
|
|
204
|
+
{ url = "https://files.pythonhosted.org/packages/21/52/dc8ee03968a5ba86e2da5aa48ddc9e3747bd65d63825fdce2d96acb9c5ff/coverage-7.15.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a524fca1a6f08927d9dc2d4c873cfb7bd7202c247f08b14bdc02424071b8b304", size = 257211, upload-time = "2026-07-12T20:56:43.513Z" },
|
|
205
|
+
{ url = "https://files.pythonhosted.org/packages/b8/27/95d7623908da8937deb53d48efcdbf423907a47540e63c62fa21372c652b/coverage-7.15.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d70f3542cd38de85a9e257dcb1ac4c1ab4b6d7d2c2a645809207556628755d1c", size = 259473, upload-time = "2026-07-12T20:56:44.974Z" },
|
|
206
|
+
{ url = "https://files.pythonhosted.org/packages/f1/3b/730d761928de97d585465680b568ae69622fb40716babadeabffe75cb51b/coverage-7.15.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d78aa537237212c4313aabe5e964b66acc86350ed19ebc56a3e202df33b6077b", size = 253759, upload-time = "2026-07-12T20:56:46.615Z" },
|
|
207
|
+
{ url = "https://files.pythonhosted.org/packages/f1/fc/6b9277acff1f9484b6c12857af5774689d1a6a95e13265f7405329d2f5da/coverage-7.15.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a318112bb4f79d9d04766196d5a3388caa825908a6a9b052aa87de3d9aea7c61", size = 255131, upload-time = "2026-07-12T20:56:48.073Z" },
|
|
208
|
+
{ url = "https://files.pythonhosted.org/packages/3d/f2/c704f86129594ba34e25a64695d2068c71d51c2b98907184d716c94f4aec/coverage-7.15.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e55d24cada901963eed5bc89fa562aa033f0d84b9d3de4ecf363737c13aed11e", size = 253275, upload-time = "2026-07-12T20:56:49.538Z" },
|
|
209
|
+
{ url = "https://files.pythonhosted.org/packages/f6/29/80fee8af47de4a6dce71ccf2938491f444687a756af258a56d8469b8f1b0/coverage-7.15.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3c78f0cea7275342cf2adc2ad5fdd0aafa106ad91e66d573568f2fcf62c41df5", size = 257345, upload-time = "2026-07-12T20:56:51.038Z" },
|
|
210
|
+
{ url = "https://files.pythonhosted.org/packages/20/21/a1e7d7ed1b48a8adf8fd5154d9e83fcc5ad8e6ff20ae00e44865057dce8d/coverage-7.15.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:86bd37eabe39977216f630a7fc1b698e7f5e81a191c7186013245c6c3d313f9d", size = 252844, upload-time = "2026-07-12T20:56:52.535Z" },
|
|
211
|
+
{ url = "https://files.pythonhosted.org/packages/a7/8c/a4bc26e6ee207d412f3678f04d74be1550e83140563ca0e4997510579712/coverage-7.15.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c6db15c217693bdc3ca0b84de1ba9afafe1c14c26a8a29d77f4ed0de2b6132e2", size = 254716, upload-time = "2026-07-12T20:56:53.968Z" },
|
|
212
|
+
{ url = "https://files.pythonhosted.org/packages/11/9d/8ad0266ecfada6353cf6627a1a02294cf55a907521b6ee0bd7b770cfd659/coverage-7.15.1-cp312-cp312-win32.whl", hash = "sha256:359f3fbe09a51500c51966596ee4ee4070b356552c70b3b2420eb200d68e0f76", size = 223554, upload-time = "2026-07-12T20:56:55.583Z" },
|
|
213
|
+
{ url = "https://files.pythonhosted.org/packages/81/6d/24224929e06c6e05a93f738bc5f9e8e6ab658f8f1d9b823e7b85430e28b8/coverage-7.15.1-cp312-cp312-win_amd64.whl", hash = "sha256:fa75dc099c126e941a9c0baa8ebd2cbc78bd778687534fe410baf754f6d9e374", size = 224087, upload-time = "2026-07-12T20:56:57.041Z" },
|
|
214
|
+
{ url = "https://files.pythonhosted.org/packages/35/23/f81441dd01de88e53c97842e706907b307d9078918c3f4998b11e9ac7250/coverage-7.15.1-cp312-cp312-win_arm64.whl", hash = "sha256:26f89cf6d0634375f454fa71057945ad18edb0f1607a90fecf22c57dc3dc289a", size = 223472, upload-time = "2026-07-12T20:56:58.594Z" },
|
|
215
|
+
{ url = "https://files.pythonhosted.org/packages/ca/1e/6fa289d7993a2a39f1b283ddb58c4bfec80f7800be654b8ba8a9f6a07c63/coverage-7.15.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:71ac4ca1658ca99160fd58cc6967110e989c34b04627f24ed6ec9f70fb24571a", size = 221519, upload-time = "2026-07-12T20:57:00.081Z" },
|
|
216
|
+
{ url = "https://files.pythonhosted.org/packages/6f/e1/0db4902a0588234a70ab0218073c0b20fbc5c740aa35f91d360160a2ebc9/coverage-7.15.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:26a40cbf2b13bd94af53ee02a424cb3bb96a9edfac0d00834bd068512a62714b", size = 221895, upload-time = "2026-07-12T20:57:01.867Z" },
|
|
217
|
+
{ url = "https://files.pythonhosted.org/packages/b4/cb/3719783865092dac5e08df842730305ee9ab1973ae7ddb6fbdf27d401f30/coverage-7.15.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f4c5a5eff4ad4f9f7088fd3fc7a66d98d06566ee294b3b053309fb0a3b45be1e", size = 252882, upload-time = "2026-07-12T20:57:03.459Z" },
|
|
218
|
+
{ url = "https://files.pythonhosted.org/packages/1c/5e/caf3abbdbb22629626160ffc9c017eb995b7cb11c0be46b974834cef1792/coverage-7.15.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:962aa56c1c9b016d681265880eb6acc9966029d2c4c559319cc43a1abbb9b59a", size = 255479, upload-time = "2026-07-12T20:57:04.984Z" },
|
|
219
|
+
{ url = "https://files.pythonhosted.org/packages/d6/f1/d60f375bfe095fef944f0f19427aefdbf9bdd5a9571c41a4bf6e2f5fdb81/coverage-7.15.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1678eb2dc57a8ce67601b029582ef6d41e9e6ca22692aaeccd4107e40f27386c", size = 256715, upload-time = "2026-07-12T20:57:06.446Z" },
|
|
220
|
+
{ url = "https://files.pythonhosted.org/packages/d7/17/8b0cbc90d02dc5adad4d9034c1824ec3fa567771b4c39d9c1e3f9b1431b8/coverage-7.15.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1174900a43f6f8c425fee10d7dbddc308adefcdc78aaced32357f5ab750a0e90", size = 258845, upload-time = "2026-07-12T20:57:08.092Z" },
|
|
221
|
+
{ url = "https://files.pythonhosted.org/packages/92/29/c5e69f5fb75c322e9a3e4ef64d02eebfc3d66efceccc8514ff80a3c13a56/coverage-7.15.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:98847557a6859cadf693792ce89f440cb89692993f60dc6d3a7e35f3d340216f", size = 253098, upload-time = "2026-07-12T20:57:09.636Z" },
|
|
222
|
+
{ url = "https://files.pythonhosted.org/packages/64/57/21144252fdd0c01d707d48fbcea13a80b0b7c42ced3f299f885ab8978c3a/coverage-7.15.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8697b2edb57143546a24389efc11e1b000cd5800fc20d84f04edb601e4a7cfb8", size = 254844, upload-time = "2026-07-12T20:57:11.141Z" },
|
|
223
|
+
{ url = "https://files.pythonhosted.org/packages/59/2a/499a28a322b0ce6768328e6c5bb2e2ad00ac068a7c7adb2ecd8533c8c5d9/coverage-7.15.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6827ac0519be3fe91bf96b4060eb00d1d24f82649b29862cd75a3cfca248b02a", size = 252807, upload-time = "2026-07-12T20:57:12.678Z" },
|
|
224
|
+
{ url = "https://files.pythonhosted.org/packages/2c/6f/928a95da5da8b60f2b00e1482c7787b3316188e6d2d227fb8e124ada43a1/coverage-7.15.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:2de8ecbbc77c7e4d22572779920ed8979c69168675e96be3a548c996568c6c31", size = 256965, upload-time = "2026-07-12T20:57:14.326Z" },
|
|
225
|
+
{ url = "https://files.pythonhosted.org/packages/16/10/889adbc1b8c9f866ed51e18a98bcafc0259fb9d29b81f50a719407c64ea8/coverage-7.15.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2b25f0f0fa5260df9d7bb55d47c8bdc23fa3382c1a18f7c9cae122e6c320b1ad", size = 252628, upload-time = "2026-07-12T20:57:15.892Z" },
|
|
226
|
+
{ url = "https://files.pythonhosted.org/packages/1a/30/a5e1871e5d93416511f8e359d1ccebfe0cbb050a1bbf7dd20228533ec0cf/coverage-7.15.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a2effcbd93ae340a58db718fe4181d967f84d352c4cefeaab4ff82ce813901a", size = 254399, upload-time = "2026-07-12T20:57:17.703Z" },
|
|
227
|
+
{ url = "https://files.pythonhosted.org/packages/2d/26/c36fbffd549dadbdd1a75827528fb00a4c46aa3187b007b750b1e2cebbf2/coverage-7.15.1-cp313-cp313-win32.whl", hash = "sha256:895e65c96aef0cecea250f6e35e9a32f11375514e1a0cb5210e0fda128c04e8e", size = 223564, upload-time = "2026-07-12T20:57:19.253Z" },
|
|
228
|
+
{ url = "https://files.pythonhosted.org/packages/16/fc/becbb9d2c4206d242b9b1e1e8e24a42f7926c0200dd3c788b9fab4bb96d5/coverage-7.15.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6d0a28b63a0d75f9ed5118105d1154fc3aa40a8605a30d5d87e3d043ad90fe7", size = 224106, upload-time = "2026-07-12T20:57:21.108Z" },
|
|
229
|
+
{ url = "https://files.pythonhosted.org/packages/d3/30/1cfc641461369b6858799fca61c0a8b5edc490c519bf7c636ffa6bbf556f/coverage-7.15.1-cp313-cp313-win_arm64.whl", hash = "sha256:b4ee9818e8bae3544379ad2c09b851c4fb886aaa8860d57a1c1316ddcb16db49", size = 223497, upload-time = "2026-07-12T20:57:22.734Z" },
|
|
230
|
+
{ url = "https://files.pythonhosted.org/packages/f0/46/81961952e7aebfb38ad0ae4264e8954cc607a7af9e7ac111f9fa986595cc/coverage-7.15.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a886af95f59edf67d5770fd3564d53f4a8af93f25f8c1d60d27e00d7f5674ee8", size = 221560, upload-time = "2026-07-12T20:57:24.282Z" },
|
|
231
|
+
{ url = "https://files.pythonhosted.org/packages/13/d2/ee14d715889f216baf47301d9f469e08fff6995552aaf67e897b282865f6/coverage-7.15.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:985657ebd707941de90d488d1cbb5efac20bdf81f7b91eba771624ccda4d36f4", size = 221894, upload-time = "2026-07-12T20:57:25.87Z" },
|
|
232
|
+
{ url = "https://files.pythonhosted.org/packages/f3/38/f830bc6e6c2c5f23f43847125e6c650d378872f7eeba8d49f1d42193e8a9/coverage-7.15.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5bbe2a06e0a5e1404d9ffbdb49b819bbd6a3bb198ebea4c8dfe7ad9f1e1c2e81", size = 252938, upload-time = "2026-07-12T20:57:27.506Z" },
|
|
233
|
+
{ url = "https://files.pythonhosted.org/packages/e1/53/0d3dd963631259d794c898735d5436e68d6a8d40749c419a07ff7c171469/coverage-7.15.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bde0fe24083d0b7b3dbafa7a09f0796410af1afa2523f28f5f208d8340a4aaca", size = 255445, upload-time = "2026-07-12T20:57:29.234Z" },
|
|
234
|
+
{ url = "https://files.pythonhosted.org/packages/b1/fd/aabed228557565c958259251b89bab8c5669b31291fa63b3e2154ebb017a/coverage-7.15.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f89f7453d6d46db14cf233e2cd8edcd78de2b9c49d4f1dc109590b4e5dbfbb74", size = 256790, upload-time = "2026-07-12T20:57:30.826Z" },
|
|
235
|
+
{ url = "https://files.pythonhosted.org/packages/bc/aa/1cc888e5d3623e603c4e5399653cb25728bb2b40d7519188a3e293d24620/coverage-7.15.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cc3656c9ecc27b36bd0907455b77f83c0069ca9ad4a66dec892b76c696eb6047", size = 259104, upload-time = "2026-07-12T20:57:32.63Z" },
|
|
236
|
+
{ url = "https://files.pythonhosted.org/packages/5f/61/fc16d5f5e53098dae41efa21e8ccc611a9b4fe922750dd03dc56db552182/coverage-7.15.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:24d8e85a2a45e44883b488c2659f51fa761dad5353fdb319b672a93facbd2ca9", size = 252956, upload-time = "2026-07-12T20:57:34.316Z" },
|
|
237
|
+
{ url = "https://files.pythonhosted.org/packages/a1/f3/52384668c3de4519ca770bf1975a89e4d6eb5aa2faf0da0577a14008cba4/coverage-7.15.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:68931b5fe746ed4fdaa8892989cab9e6c35781eeb3b0ab2ded893d561e1b3652", size = 254797, upload-time = "2026-07-12T20:57:35.947Z" },
|
|
238
|
+
{ url = "https://files.pythonhosted.org/packages/ce/68/54b807e7c1868178e902fd8360b5d4e559394462f97285c50edf1c4608db/coverage-7.15.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:1ce6947e2a95534ecaa5a15e73c21e550514c980d80eda204d064d789a95f6a4", size = 252762, upload-time = "2026-07-12T20:57:37.856Z" },
|
|
239
|
+
{ url = "https://files.pythonhosted.org/packages/7b/48/dde8adf0338e3ace738757dccf1ce817e5fdcadfae77e1b48a77e5a3b265/coverage-7.15.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:841befdbc89b9c82435fc25b0f4f41858b6238693e45af758bec4cfc1968171c", size = 257037, upload-time = "2026-07-12T20:57:39.488Z" },
|
|
240
|
+
{ url = "https://files.pythonhosted.org/packages/07/f2/179dd88cf60a0aeeee16a970ffe250dccea8b80ed4beab4c5d3f6c41ad4b/coverage-7.15.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5d3de58b837375e7f4c0e1a088ccab5f655efb2fd7427b729df02c862a559633", size = 252577, upload-time = "2026-07-12T20:57:41.363Z" },
|
|
241
|
+
{ url = "https://files.pythonhosted.org/packages/2c/37/8a593d69ab521beb6a105a2017cac4ba94425ee0a8349e29c3c0b522d24f/coverage-7.15.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b1801963f9f44ae0c0f6d737bc7aeb2bbcde7d1fe7e3b43cddc1961af42d3b41", size = 254235, upload-time = "2026-07-12T20:57:43.025Z" },
|
|
242
|
+
{ url = "https://files.pythonhosted.org/packages/6d/34/bc9b3bced66f2cdad4bf5e57ae51c54ea226e8aaaebfc9370a9a11877bf3/coverage-7.15.1-cp314-cp314-win32.whl", hash = "sha256:8c7953c4128ef53b6ffb5f90d87c87d4ce26731df294760bb2314eb0e069e44b", size = 223771, upload-time = "2026-07-12T20:57:44.662Z" },
|
|
243
|
+
{ url = "https://files.pythonhosted.org/packages/4c/f3/4d20337bed61915d14349e62b88d5e4144d5a9872b64adbe90e9906db6db/coverage-7.15.1-cp314-cp314-win_amd64.whl", hash = "sha256:6f0bab60a582d415f0fb535ccff13ba334a47a1538f98913330a525d23bd535a", size = 224257, upload-time = "2026-07-12T20:57:46.412Z" },
|
|
244
|
+
{ url = "https://files.pythonhosted.org/packages/7b/df/bbfeae4948f3ded516f92b32f2d57952427fc5ecfc0924487bb6ee6a5f38/coverage-7.15.1-cp314-cp314-win_arm64.whl", hash = "sha256:0f410ee8f0ac4ec7db71bc0b7632a8b9994e1cad2755bd1566c17e6a162caa74", size = 223683, upload-time = "2026-07-12T20:57:48.106Z" },
|
|
245
|
+
{ url = "https://files.pythonhosted.org/packages/35/65/0b431856064e387d1f5cf474625e4a0465e907024d42f35de6af19ced0be/coverage-7.15.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:fc868bab88e049d41fcd41766810d790a8b960053be2a45e060f5ce0d31d258b", size = 222298, upload-time = "2026-07-12T20:57:49.882Z" },
|
|
246
|
+
{ url = "https://files.pythonhosted.org/packages/a6/96/50eac9bd49df8a3df5f3d38746d1bf332299dffb554486c94ebd55c9dc49/coverage-7.15.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:206d4ec6028f2773b40932d09f074539d6bcdd8f6b318d40cb04bdbd68ed0b49", size = 222561, upload-time = "2026-07-12T20:57:51.688Z" },
|
|
247
|
+
{ url = "https://files.pythonhosted.org/packages/e0/5b/6ba1c4a27e10b8816fd2622b98162c83d3bdf1185097360373611bf96364/coverage-7.15.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:620482ef1c9f4e61f962e159325fe77dea59d16e39d9c9470d069053b244d864", size = 263923, upload-time = "2026-07-12T20:57:53.392Z" },
|
|
248
|
+
{ url = "https://files.pythonhosted.org/packages/e4/59/fe03ade97a3ca2d890e98c572cf48a99fda9adba85757c34b823f41efe1e/coverage-7.15.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d385fc9b054e309ad3cecdc77b586d2af0c98aeec2fdb3773544586f366e817c", size = 266043, upload-time = "2026-07-12T20:57:55.095Z" },
|
|
249
|
+
{ url = "https://files.pythonhosted.org/packages/16/e0/55c4b1217a572a43e13b39e1eb78d0da29fb23679003bd0cdf22c50b1978/coverage-7.15.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1198bca9c0dd7c188aae1f185b0c0b5fc4f0a2b6909000858c29550320bdb07", size = 268465, upload-time = "2026-07-12T20:57:57.017Z" },
|
|
250
|
+
{ url = "https://files.pythonhosted.org/packages/b1/e2/ee47944f76afc03909119b036fe9e0da8cbd274a5141287de79791a0fb6d/coverage-7.15.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5d0297e6a070eadb49df7cddd0ab6f420b8b689dd8904c7dd815a323168fa57e", size = 269584, upload-time = "2026-07-12T20:57:58.958Z" },
|
|
251
|
+
{ url = "https://files.pythonhosted.org/packages/cb/8a/6b4d9779c7b2e21c3d12c3425e3261aa7411399319e27aa402dfec4db5d0/coverage-7.15.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:916fcf2214f56960e409561b37fc32a160a42b6e85483d0652d7b70fa55d707e", size = 263019, upload-time = "2026-07-12T20:58:00.979Z" },
|
|
252
|
+
{ url = "https://files.pythonhosted.org/packages/c4/1e/db5c7fa0c8ba5ece390a1e1a3f30db71d440240a80589df28e66a7503c40/coverage-7.15.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f837bae572c7869ffaa502e604c87e182543012831cf87aae4586ad090ac6dcf", size = 265916, upload-time = "2026-07-12T20:58:03.005Z" },
|
|
253
|
+
{ url = "https://files.pythonhosted.org/packages/83/53/fe5176682b00709b13fab36addd26883139d0dea430816fea412e69255e2/coverage-7.15.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3ea65e3ee6c7c32349fd00559927a9e577bdd72386087eeed1c42b62dfce9b82", size = 263520, upload-time = "2026-07-12T20:58:04.994Z" },
|
|
254
|
+
{ url = "https://files.pythonhosted.org/packages/f9/e7/16f15127be93fbc70c667df5ec5dce934fc76c9b0888d84969a5d5341e2c/coverage-7.15.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:345034976f46a1c54bd17f4e43eb30bb92cb7082fcddff03250cff136cc4eb82", size = 267254, upload-time = "2026-07-12T20:58:06.824Z" },
|
|
255
|
+
{ url = "https://files.pythonhosted.org/packages/cd/73/e5119111f6f065376395a525f7ce6e9174d83f3db6d217ea0211a61cca4d/coverage-7.15.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4f051a64eb8f8addb4661c2b41d6eea5b7ebc68ad4b2baea8d9bc54e1956e5f7", size = 262366, upload-time = "2026-07-12T20:58:08.555Z" },
|
|
256
|
+
{ url = "https://files.pythonhosted.org/packages/d7/9c/6d0a81182df18a73b081e7a8630f0e2a52b12dfd7898c6ab839551a454d2/coverage-7.15.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a7625770f7720b49bb30d194ad2f8d50fab3c5177874af3d2399676f95f9c594", size = 264680, upload-time = "2026-07-12T20:58:10.359Z" },
|
|
257
|
+
{ url = "https://files.pythonhosted.org/packages/d9/a2/bac0cbd4450638f1be2041a464b1766c8cc94abf705a2df6f1c8d4be870d/coverage-7.15.1-cp314-cp314t-win32.whl", hash = "sha256:81e503d130a472ad1bd38199ecd35116b40d92bcd31e27a2cacde035381f2070", size = 224077, upload-time = "2026-07-12T20:58:12.065Z" },
|
|
258
|
+
{ url = "https://files.pythonhosted.org/packages/f0/b2/d83c5403155172a43ba47c08641bad3f89822d8405102423a41339d2c857/coverage-7.15.1-cp314-cp314t-win_amd64.whl", hash = "sha256:724e878b213b302ad46e9f2fc872d386613f20ebfc492a211482d917ea76c14f", size = 224908, upload-time = "2026-07-12T20:58:13.956Z" },
|
|
259
|
+
{ url = "https://files.pythonhosted.org/packages/cc/41/442b74cad832cc77712080585455482e7cc4f4a9a13192f65731dcd18231/coverage-7.15.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ce2f05c14d077f406fefc4fa5e4f093ad0e0787549f6582535d6e28766f0361b", size = 224219, upload-time = "2026-07-12T20:58:16.315Z" },
|
|
260
|
+
{ url = "https://files.pythonhosted.org/packages/34/98/07a67cf1a26e795d617ed5c540c042b0ac87b72f810c30c07f076cf334f3/coverage-7.15.1-py3-none-any.whl", hash = "sha256:717d01e6e00bed56ad13306f19e0dd2f4f645ee8159d2c72c72301d6cfc7090c", size = 213284, upload-time = "2026-07-12T20:58:18.079Z" },
|
|
261
|
+
]
|
|
262
|
+
|
|
263
|
+
[package.optional-dependencies]
|
|
264
|
+
toml = [
|
|
265
|
+
{ name = "tomli", marker = "python_full_version <= '3.11'" },
|
|
266
|
+
]
|
|
267
|
+
|
|
179
268
|
[[package]]
|
|
180
269
|
name = "cryptography"
|
|
181
270
|
version = "49.0.0"
|
|
@@ -395,7 +484,7 @@ wheels = [
|
|
|
395
484
|
|
|
396
485
|
[[package]]
|
|
397
486
|
name = "postgres-aiops"
|
|
398
|
-
version = "0.
|
|
487
|
+
version = "0.2.0"
|
|
399
488
|
source = { editable = "." }
|
|
400
489
|
dependencies = [
|
|
401
490
|
{ name = "cryptography" },
|
|
@@ -409,6 +498,7 @@ dependencies = [
|
|
|
409
498
|
[package.dev-dependencies]
|
|
410
499
|
dev = [
|
|
411
500
|
{ name = "pytest" },
|
|
501
|
+
{ name = "pytest-cov" },
|
|
412
502
|
{ name = "ruff" },
|
|
413
503
|
]
|
|
414
504
|
|
|
@@ -425,6 +515,7 @@ requires-dist = [
|
|
|
425
515
|
[package.metadata.requires-dev]
|
|
426
516
|
dev = [
|
|
427
517
|
{ name = "pytest", specifier = ">=8.0,<10.0" },
|
|
518
|
+
{ name = "pytest-cov", specifier = ">=5.0,<8.0" },
|
|
428
519
|
{ name = "ruff", specifier = ">=0.5,<1.0" },
|
|
429
520
|
]
|
|
430
521
|
|
|
@@ -676,6 +767,20 @@ wheels = [
|
|
|
676
767
|
{ url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" },
|
|
677
768
|
]
|
|
678
769
|
|
|
770
|
+
[[package]]
|
|
771
|
+
name = "pytest-cov"
|
|
772
|
+
version = "7.1.0"
|
|
773
|
+
source = { registry = "https://pypi.org/simple" }
|
|
774
|
+
dependencies = [
|
|
775
|
+
{ name = "coverage", extra = ["toml"] },
|
|
776
|
+
{ name = "pluggy" },
|
|
777
|
+
{ name = "pytest" },
|
|
778
|
+
]
|
|
779
|
+
sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" }
|
|
780
|
+
wheels = [
|
|
781
|
+
{ url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" },
|
|
782
|
+
]
|
|
783
|
+
|
|
679
784
|
[[package]]
|
|
680
785
|
name = "python-dotenv"
|
|
681
786
|
version = "1.2.2"
|
|
@@ -981,6 +1086,60 @@ wheels = [
|
|
|
981
1086
|
{ url = "https://files.pythonhosted.org/packages/ec/bb/2799cc2ede3ed41131f8975621e7213dfc7ef4acbbaadfa440f32500c370/starlette-1.3.1-py3-none-any.whl", hash = "sha256:c7372aae11c3c3f26a42df7bd626cec2f47d03483d261d369516a615a53714c6", size = 73632, upload-time = "2026-06-12T09:23:10.017Z" },
|
|
982
1087
|
]
|
|
983
1088
|
|
|
1089
|
+
[[package]]
|
|
1090
|
+
name = "tomli"
|
|
1091
|
+
version = "2.4.1"
|
|
1092
|
+
source = { registry = "https://pypi.org/simple" }
|
|
1093
|
+
sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" }
|
|
1094
|
+
wheels = [
|
|
1095
|
+
{ url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" },
|
|
1096
|
+
{ url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" },
|
|
1097
|
+
{ url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" },
|
|
1098
|
+
{ url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" },
|
|
1099
|
+
{ url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" },
|
|
1100
|
+
{ url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" },
|
|
1101
|
+
{ url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" },
|
|
1102
|
+
{ url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" },
|
|
1103
|
+
{ url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" },
|
|
1104
|
+
{ url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" },
|
|
1105
|
+
{ url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" },
|
|
1106
|
+
{ url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" },
|
|
1107
|
+
{ url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" },
|
|
1108
|
+
{ url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" },
|
|
1109
|
+
{ url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" },
|
|
1110
|
+
{ url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" },
|
|
1111
|
+
{ url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" },
|
|
1112
|
+
{ url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" },
|
|
1113
|
+
{ url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" },
|
|
1114
|
+
{ url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" },
|
|
1115
|
+
{ url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" },
|
|
1116
|
+
{ url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" },
|
|
1117
|
+
{ url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" },
|
|
1118
|
+
{ url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" },
|
|
1119
|
+
{ url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" },
|
|
1120
|
+
{ url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" },
|
|
1121
|
+
{ url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" },
|
|
1122
|
+
{ url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" },
|
|
1123
|
+
{ url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" },
|
|
1124
|
+
{ url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" },
|
|
1125
|
+
{ url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" },
|
|
1126
|
+
{ url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" },
|
|
1127
|
+
{ url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" },
|
|
1128
|
+
{ url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" },
|
|
1129
|
+
{ url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" },
|
|
1130
|
+
{ url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" },
|
|
1131
|
+
{ url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" },
|
|
1132
|
+
{ url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" },
|
|
1133
|
+
{ url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" },
|
|
1134
|
+
{ url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" },
|
|
1135
|
+
{ url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" },
|
|
1136
|
+
{ url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" },
|
|
1137
|
+
{ url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" },
|
|
1138
|
+
{ url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" },
|
|
1139
|
+
{ url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" },
|
|
1140
|
+
{ url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" },
|
|
1141
|
+
]
|
|
1142
|
+
|
|
984
1143
|
[[package]]
|
|
985
1144
|
name = "typer"
|
|
986
1145
|
version = "0.26.8"
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
"""postgres-aiops — governed PostgreSQL DBA operations for AI agents.
|
|
2
|
-
|
|
3
|
-
Standalone and self-contained: the governance harness (audit, token budget,
|
|
4
|
-
undo-token recording, graduated risk tiers, prompt-injection sanitize) is
|
|
5
|
-
bundled under ``postgres_aiops.governance`` — this package has no external
|
|
6
|
-
skill-family dependency. Preview: not yet full-coverage.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
__version__ = "0.1.0"
|
|
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
|
{postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/skills/postgres-aiops/references/capabilities.md
RENAMED
|
File without changes
|
{postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/skills/postgres-aiops/references/cli-reference.md
RENAMED
|
File without changes
|
{postgres_aiops-0.1.1 → postgres_aiops-0.2.0}/skills/postgres-aiops/references/setup-guide.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|