devops-sentinel 0.1.0__py3-none-any.whl
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.
- devops_sentinel-0.1.0.dist-info/METADATA +185 -0
- devops_sentinel-0.1.0.dist-info/RECORD +51 -0
- devops_sentinel-0.1.0.dist-info/WHEEL +5 -0
- devops_sentinel-0.1.0.dist-info/entry_points.txt +2 -0
- devops_sentinel-0.1.0.dist-info/top_level.txt +1 -0
- src/__init__.py +3 -0
- src/api/gdpr_endpoints.py +404 -0
- src/api/quick_health_check.py +296 -0
- src/api/waitlist.py +166 -0
- src/auth/auth_helper.py +198 -0
- src/auth/auth_service.py +431 -0
- src/cli/__init__.py +5 -0
- src/cli/auth.py +456 -0
- src/cli/db.py +212 -0
- src/cli/main.py +436 -0
- src/cli/projects.py +109 -0
- src/cli/services.py +164 -0
- src/core/__init__.py +30 -0
- src/core/alert_explainer.py +370 -0
- src/core/alert_router.py +300 -0
- src/core/anomaly_detector.py +375 -0
- src/core/auto_runbook_executor.py +492 -0
- src/core/baseline_monitor.py +301 -0
- src/core/byok_tier.py +168 -0
- src/core/classifier.py +275 -0
- src/core/cost_tracker.py +324 -0
- src/core/dependency_analyzer.py +388 -0
- src/core/deployment_correlator.py +401 -0
- src/core/incident_memory.py +436 -0
- src/core/on_call_manager.py +407 -0
- src/core/postmortem_generator.py +373 -0
- src/core/rate_limiter.py +337 -0
- src/core/runbook_matcher.py +416 -0
- src/core/service_map_generator.py +395 -0
- src/core/team_assignment_system.py +368 -0
- src/integrations/email_notification_system.py +464 -0
- src/integrations/github_oauth.py +168 -0
- src/integrations/integrations_hub.py +182 -0
- src/integrations/pagerduty_integration.py +314 -0
- src/integrations/pagerduty_oauth.py +162 -0
- src/integrations/slack.py +458 -0
- src/integrations/slack_integration.py +425 -0
- src/integrations/slack_oauth.py +295 -0
- src/ml/__init__.py +10 -0
- src/ml/anomaly_detector.py +336 -0
- src/notifications/email_templates.py +508 -0
- src/setup/ai_setup.py +187 -0
- src/setup/supabase_setup.py +295 -0
- src/tools/chaos_engineering_tools.py +448 -0
- src/tools/custom_health_check_tool.py +317 -0
- src/tools/ssl_certificate_monitor.py +221 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: devops-sentinel
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Autonomous SRE agents that watch your services, detect anomalies, and generate blameless postmortems
|
|
5
|
+
Author-email: "jagadeep.mamidi" <jagadeep.mamidi@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/devops-sentinel/devops-sentinel
|
|
8
|
+
Project-URL: Documentation, https://devops-sentinel.dev/docs
|
|
9
|
+
Project-URL: Repository, https://github.com/devops-sentinel/devops-sentinel
|
|
10
|
+
Project-URL: Issues, https://github.com/devops-sentinel/devops-sentinel/issues
|
|
11
|
+
Keywords: devops,sre,monitoring,incident-management,ai,postmortem
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: System Administrators
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: System :: Monitoring
|
|
22
|
+
Classifier: Topic :: System :: Systems Administration
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
Requires-Dist: click>=8.0
|
|
26
|
+
Requires-Dist: rich>=13.0
|
|
27
|
+
Requires-Dist: httpx>=0.25
|
|
28
|
+
Requires-Dist: aiohttp>=3.9
|
|
29
|
+
Requires-Dist: python-dotenv>=1.0
|
|
30
|
+
Requires-Dist: supabase>=2.0
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
33
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
|
|
34
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
35
|
+
Provides-Extra: ai
|
|
36
|
+
Requires-Dist: openai>=1.0; extra == "ai"
|
|
37
|
+
Requires-Dist: anthropic>=0.18; extra == "ai"
|
|
38
|
+
Provides-Extra: all
|
|
39
|
+
Requires-Dist: devops-sentinel[ai,dev]; extra == "all"
|
|
40
|
+
|
|
41
|
+
# DevOps Sentinel
|
|
42
|
+
|
|
43
|
+
**Autonomous SRE Agent System** - Reduce Mean Time to Detection (MTTD) from minutes to under 10 seconds.
|
|
44
|
+
|
|
45
|
+
## Features
|
|
46
|
+
|
|
47
|
+
- **Continuous Monitoring** - Health checks at configurable intervals
|
|
48
|
+
- **Multi-Agent Analysis** - CrewAI-powered investigation and root cause analysis
|
|
49
|
+
- **AI Postmortems** - Automated incident documentation
|
|
50
|
+
- **Real-time Dashboard** - WebSocket-powered live updates
|
|
51
|
+
- **CLI Interface** - Similar to Gemini CLI
|
|
52
|
+
- **Pre-Deploy Linting** - Code quality checks before production
|
|
53
|
+
- **Privacy-First** - No telemetry, transparent data handling
|
|
54
|
+
|
|
55
|
+
## Architecture
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
59
|
+
│ DevOps Sentinel │
|
|
60
|
+
├─────────────────────────────────────────────────────────────┤
|
|
61
|
+
│ CLI (Typer) │ API (FastAPI) │ Dashboard (WebSocket) │
|
|
62
|
+
├─────────────────────────────────────────────────────────────┤
|
|
63
|
+
│ Monitoring Orchestrator │
|
|
64
|
+
├─────────────────────────────────────────────────────────────┤
|
|
65
|
+
│ Watcher Agent │ Triage Agent │ Investigator │ Strategist │
|
|
66
|
+
├─────────────────────────────────────────────────────────────┤
|
|
67
|
+
│ Health Check │ Slack Alert │ Log Analysis │ DB Health │
|
|
68
|
+
├─────────────────────────────────────────────────────────────┤
|
|
69
|
+
│ Supabase (Persistence) │ OpenRouter (LLM) │ Slack │
|
|
70
|
+
└─────────────────────────────────────────────────────────────┘
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Installation
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
# Clone the repository
|
|
77
|
+
git clone https://github.com/yourusername/devops-sentinel.git
|
|
78
|
+
cd devops-sentinel
|
|
79
|
+
|
|
80
|
+
# Create virtual environment
|
|
81
|
+
python -m venv venv
|
|
82
|
+
source venv/bin/activate # Windows: venv\Scripts\activate
|
|
83
|
+
|
|
84
|
+
# Install dependencies
|
|
85
|
+
pip install -e .
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Quick Start
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
# Initialize configuration
|
|
92
|
+
sentinel init
|
|
93
|
+
|
|
94
|
+
# Monitor a service
|
|
95
|
+
sentinel monitor https://api.example.com/health
|
|
96
|
+
|
|
97
|
+
# Start dashboard
|
|
98
|
+
sentinel serve
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## CLI Commands
|
|
102
|
+
|
|
103
|
+
| Command | Description |
|
|
104
|
+
|---------|-------------|
|
|
105
|
+
| `sentinel init` | Interactive configuration setup |
|
|
106
|
+
| `sentinel monitor <url>` | Monitor a service URL |
|
|
107
|
+
| `sentinel status` | Show monitored services |
|
|
108
|
+
| `sentinel incidents` | List recent incidents |
|
|
109
|
+
| `sentinel postmortem <id>` | View incident postmortem |
|
|
110
|
+
| `sentinel serve` | Start dashboard server |
|
|
111
|
+
| `sentinel lint <path>` | Run pre-deploy checks |
|
|
112
|
+
|
|
113
|
+
## Configuration
|
|
114
|
+
|
|
115
|
+
Create a `.env` file or run `sentinel init`:
|
|
116
|
+
|
|
117
|
+
```env
|
|
118
|
+
OPENROUTER_API_KEY=your_key_here
|
|
119
|
+
DEFAULT_MODEL=google/gemini-pro
|
|
120
|
+
SUPABASE_URL=https://your-project.supabase.co
|
|
121
|
+
SUPABASE_KEY=your_anon_key
|
|
122
|
+
SLACK_WEBHOOK_URL=https://hooks.slack.com/...
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Docker Deployment
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
# Build and run
|
|
129
|
+
docker-compose up -d
|
|
130
|
+
|
|
131
|
+
# View logs
|
|
132
|
+
docker-compose logs -f sentinel
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## How It Works
|
|
136
|
+
|
|
137
|
+
1. **Watcher Agent** continuously monitors service health endpoints
|
|
138
|
+
2. On failure, **First Responder** sends a Slack alert
|
|
139
|
+
3. **Investigator** analyzes logs, deployments, and database status
|
|
140
|
+
4. **Strategist** creates a prioritized action plan
|
|
141
|
+
5. AI generates a structured postmortem saved to Supabase
|
|
142
|
+
|
|
143
|
+
## Privacy
|
|
144
|
+
|
|
145
|
+
- All data stays local or in YOUR Supabase instance
|
|
146
|
+
- No telemetry or external data collection
|
|
147
|
+
- Use `--privacy` flag to see exactly what data is processed
|
|
148
|
+
- OpenRouter calls only contain health check context (no PII)
|
|
149
|
+
|
|
150
|
+
## Supabase Schema
|
|
151
|
+
|
|
152
|
+
```sql
|
|
153
|
+
-- Run these in your Supabase SQL editor
|
|
154
|
+
CREATE TABLE services (
|
|
155
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
156
|
+
name TEXT NOT NULL,
|
|
157
|
+
url TEXT NOT NULL,
|
|
158
|
+
check_interval INTEGER DEFAULT 10,
|
|
159
|
+
is_active BOOLEAN DEFAULT true,
|
|
160
|
+
created_at TIMESTAMPTZ DEFAULT now()
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
CREATE TABLE incidents (
|
|
164
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
165
|
+
service_id UUID REFERENCES services(id),
|
|
166
|
+
status TEXT DEFAULT 'detecting',
|
|
167
|
+
detected_at TIMESTAMPTZ DEFAULT now(),
|
|
168
|
+
resolved_at TIMESTAMPTZ,
|
|
169
|
+
mttd_seconds FLOAT,
|
|
170
|
+
postmortem TEXT
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
CREATE TABLE health_checks (
|
|
174
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
175
|
+
service_id UUID REFERENCES services(id),
|
|
176
|
+
status_code INTEGER,
|
|
177
|
+
response_time_ms FLOAT,
|
|
178
|
+
is_healthy BOOLEAN,
|
|
179
|
+
checked_at TIMESTAMPTZ DEFAULT now()
|
|
180
|
+
);
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## License
|
|
184
|
+
|
|
185
|
+
MIT License
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
src/__init__.py,sha256=W0c771AYtfl_5vqTT3uRL0xQ50elYkhvSoSjxtFCuVw,72
|
|
2
|
+
src/api/gdpr_endpoints.py,sha256=We-73ij9hjY-JfcVhD7cF-AGCSTOMrgdGmoAgi3KMts,14865
|
|
3
|
+
src/api/quick_health_check.py,sha256=I-YVcdj3ahLEq5fzXBcf_OzXpDqA6WZPQ2N2dQaIKwI,10537
|
|
4
|
+
src/api/waitlist.py,sha256=bJUKWirEDextGpRVKRNKh0V9xDy70kudzMvspBBk07E,4617
|
|
5
|
+
src/auth/auth_helper.py,sha256=xW7pzhAtzFyMy8yH6pxdpKB-A3nbgt8YFYE4EXF1AjU,5639
|
|
6
|
+
src/auth/auth_service.py,sha256=4tyuqIdsXI1ZcmP9LjQhxflYJoqK9V7gSe0mFU-a_GQ,12982
|
|
7
|
+
src/cli/__init__.py,sha256=oiyes-3XQOlOKJdpn2pHkB-kY6334dXwb6dF6zZPk4I,81
|
|
8
|
+
src/cli/auth.py,sha256=4EyVCfOgZ2brpfvHjKopIl3lTuFtGjeKRh1mytU-DoE,14290
|
|
9
|
+
src/cli/db.py,sha256=2F9fjzPkyz8Akw7bmua2dTdTvz8FzxwS0dA1fJEhyTE,6927
|
|
10
|
+
src/cli/main.py,sha256=QqDY0SmotG6oZ9Y5SkcpB0K7SBbMKhCAHxPiU0Jycdk,17256
|
|
11
|
+
src/cli/projects.py,sha256=252slmb-7dQdKRU1aa1W4A-mD14wNsDqaKfDJlEplSw,3554
|
|
12
|
+
src/cli/services.py,sha256=KMakjc9c0mXC92FUArJ0GoQZ2KZRHIfXo1jwfPr6l1Y,5989
|
|
13
|
+
src/core/__init__.py,sha256=W19C3wGZXJgMNjkOLfQ0WFMpB8j8JQTDJBQKfeGEThY,881
|
|
14
|
+
src/core/alert_explainer.py,sha256=pAafpo_t9-jBsdKu_PygvRboa67OwLGKkrSFbm8V0Ho,14461
|
|
15
|
+
src/core/alert_router.py,sha256=MPqWkylytWQE-U_SV9w-q6pqe9GdYBB7GrzFyKDOPJw,10047
|
|
16
|
+
src/core/anomaly_detector.py,sha256=JRAlyh3jOGfwnc5xc_WfMxLYrudEAQwJ5eOF9NWNYtY,12587
|
|
17
|
+
src/core/auto_runbook_executor.py,sha256=IDVRfwpm0CuKOkw-f7gZq749kCB4srgAYaeIGl9nxj0,16974
|
|
18
|
+
src/core/baseline_monitor.py,sha256=COKICWaQFAobt2bZq0XUGNX8gcZHdppTzapoqqVZ-2s,10289
|
|
19
|
+
src/core/byok_tier.py,sha256=CMo8cyDIL_QItAYvevDnbWjH3GcT7ZZxTFVoDEfe4pI,5323
|
|
20
|
+
src/core/classifier.py,sha256=ajiNFMVmiz6lstZoptkgVjy1iyverWvpH8w5IVR-CzY,9851
|
|
21
|
+
src/core/cost_tracker.py,sha256=U4J5xu0gEmp2gfzpWD2-YYVvyrG0Hr7Fph2_4LC04lw,10481
|
|
22
|
+
src/core/dependency_analyzer.py,sha256=K4Psb8eOtfHTGtYNsvhmy3bKppyvoHdgoGWIr2iTf5A,13810
|
|
23
|
+
src/core/deployment_correlator.py,sha256=E24C1U4b0THtmHSvI_ct7QxmUXD-ovjG930xx_8F0QI,13608
|
|
24
|
+
src/core/incident_memory.py,sha256=s1IbaRsfU8tROBdwmYDR5yQ3NmKuvAf9OCv1RYBuLdI,15083
|
|
25
|
+
src/core/on_call_manager.py,sha256=MH7X2hWqTE9ht1XsvHXvqFd0PWH4qL3UbHVKEvLT8Tc,13035
|
|
26
|
+
src/core/postmortem_generator.py,sha256=HVE9r5hzM8bXAxp6KKvnKJ-esxBEYSRKzVYPXc7yosM,11687
|
|
27
|
+
src/core/rate_limiter.py,sha256=EPIyHTzyEaZhq8k1bMgO8-nqAzIoU8nwJbxCd4gjaco,10911
|
|
28
|
+
src/core/runbook_matcher.py,sha256=0HVoQwQr-NQRVGSxbS7estddi-yX4bgsELGIFA1_t3Q,13745
|
|
29
|
+
src/core/service_map_generator.py,sha256=WtPW3Nooes6Fz-0X6saOrFTrQaKVbSzNwGe_Ru7uzOw,13468
|
|
30
|
+
src/core/team_assignment_system.py,sha256=GFChh-LlbL3Xnj7mtAznF9SKpWk8uxWXu6EsfXNvVb8,12395
|
|
31
|
+
src/integrations/email_notification_system.py,sha256=KEkMiXtQyp4w2OFfM5RnA7YevBvkO4J2fFGxJaI8FNw,15886
|
|
32
|
+
src/integrations/github_oauth.py,sha256=pdIax8Woe5DaC4R-poLeBlqO9nJgUbp_vLwrVa-8ZOY,6170
|
|
33
|
+
src/integrations/integrations_hub.py,sha256=0ZpSvyygXTLe5RednCLSekWCUNO9A_r_5K7y8Kqvhho,5494
|
|
34
|
+
src/integrations/pagerduty_integration.py,sha256=HdTlb0_pSZC0GIkJYod3zlmji33ah7r_i30A0FGiuVo,11330
|
|
35
|
+
src/integrations/pagerduty_oauth.py,sha256=IJMOrlLKqkfKZQsPoS0JU1N3WbXRSYxebqDcEcZ-rn0,5771
|
|
36
|
+
src/integrations/slack.py,sha256=yROA7jeOVkaLmqThxOyLHAcWKuK4h_OkpEhD-xD9saY,14971
|
|
37
|
+
src/integrations/slack_integration.py,sha256=VROKKme2ExfwIywTzjvB4ygPFC6tTA4UALQp-kD9FTE,14849
|
|
38
|
+
src/integrations/slack_oauth.py,sha256=rd6a7Qhsy7zfidfKPRRQW3iSBBrRITYt_QM-A4vSbNs,10323
|
|
39
|
+
src/ml/__init__.py,sha256=pmCpA54JMvJ066yiASK4IQghnGC8Gmr9uA_ZVzsUwbU,210
|
|
40
|
+
src/ml/anomaly_detector.py,sha256=aMOGeuQ_urSbPBGZUOD7qPQ2QRvPc9_khXi4JEZ7mdU,11188
|
|
41
|
+
src/notifications/email_templates.py,sha256=kzzmJjYMMZa7PWI8bgjsWW0MSMEodOsYIO27YAUaThc,17768
|
|
42
|
+
src/setup/ai_setup.py,sha256=sRgfIPERgiKoib14H9fiTEizYXYzTesfh7UzCDtt1kY,7076
|
|
43
|
+
src/setup/supabase_setup.py,sha256=M6FoCeURb5mMC2QZFY0NbF1HizJ49BpVmvVId0sZEB4,10318
|
|
44
|
+
src/tools/chaos_engineering_tools.py,sha256=g9ks7lcUdhZHyGmh89HgZM8uRo5pDwPjWu8H2eS8jj4,15813
|
|
45
|
+
src/tools/custom_health_check_tool.py,sha256=0HDy_9aSWgBSdeV_jcZQHLyZq_EjZD6lkOPvcLXSKkk,10432
|
|
46
|
+
src/tools/ssl_certificate_monitor.py,sha256=LfviML9PlOiv1jEUaxl_kgZjlxjLurQhXZw3wsaoDrs,7459
|
|
47
|
+
devops_sentinel-0.1.0.dist-info/METADATA,sha256=3ihOR4u0w1Zed3iXg2iJVxiFnjt-Z1AJRKj6nvnSd6g,6941
|
|
48
|
+
devops_sentinel-0.1.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
49
|
+
devops_sentinel-0.1.0.dist-info/entry_points.txt,sha256=IzfXwip0Zs1348aDQR3r1d1uKWVT9a6CeC1wTgbPmjI,46
|
|
50
|
+
devops_sentinel-0.1.0.dist-info/top_level.txt,sha256=74rtVfumQlgAPzR5_2CgYN24MB0XARCg0t-gzk6gTrM,4
|
|
51
|
+
devops_sentinel-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
src
|
src/__init__.py
ADDED
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
"""
|
|
2
|
+
GDPR Compliance Endpoints - Data Export and Deletion
|
|
3
|
+
=====================================================
|
|
4
|
+
|
|
5
|
+
User rights: Access, Export, Delete all personal data
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from typing import Dict, Optional
|
|
10
|
+
from fastapi import APIRouter, HTTPException, Depends
|
|
11
|
+
from fastapi.responses import JSONResponse
|
|
12
|
+
import json
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
router = APIRouter(prefix="/api/user", tags=["user"])
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class GDPRHandler:
|
|
19
|
+
"""
|
|
20
|
+
Handle GDPR compliance requests
|
|
21
|
+
|
|
22
|
+
Implements:
|
|
23
|
+
- Article 15: Right of access
|
|
24
|
+
- Article 17: Right to erasure
|
|
25
|
+
- Article 20: Right to data portability
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, supabase_client):
|
|
29
|
+
self.supabase = supabase_client
|
|
30
|
+
|
|
31
|
+
async def export_all_data(self, user_id: str) -> Dict:
|
|
32
|
+
"""
|
|
33
|
+
GDPR Article 20 - Export ALL user data
|
|
34
|
+
|
|
35
|
+
Returns complete data package in JSON format
|
|
36
|
+
"""
|
|
37
|
+
export_data = {
|
|
38
|
+
'export_metadata': {
|
|
39
|
+
'user_id': user_id,
|
|
40
|
+
'export_timestamp': datetime.utcnow().isoformat(),
|
|
41
|
+
'format_version': '1.0',
|
|
42
|
+
'data_retention_policy': '2 years for incidents, 90 days for logs'
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
# Core account data
|
|
46
|
+
'account': await self._get_account_data(user_id),
|
|
47
|
+
|
|
48
|
+
# Services and monitoring
|
|
49
|
+
'services': await self._get_services(user_id),
|
|
50
|
+
'health_checks': await self._get_health_checks(user_id),
|
|
51
|
+
|
|
52
|
+
# Incidents and postmortems
|
|
53
|
+
'incidents': await self._get_incidents(user_id),
|
|
54
|
+
'postmortems': await self._get_postmortems(user_id),
|
|
55
|
+
|
|
56
|
+
# Runbooks and automation
|
|
57
|
+
'runbooks': await self._get_runbooks(user_id),
|
|
58
|
+
'runbook_executions': await self._get_runbook_executions(user_id),
|
|
59
|
+
|
|
60
|
+
# Team and collaboration
|
|
61
|
+
'team_members': await self._get_team_members(user_id),
|
|
62
|
+
'on_call_schedules': await self._get_on_call_schedules(user_id),
|
|
63
|
+
|
|
64
|
+
# AI and embeddings (CRITICAL for GDPR)
|
|
65
|
+
'embeddings': await self._get_embeddings(user_id),
|
|
66
|
+
'ai_usage_logs': await self._get_ai_usage(user_id),
|
|
67
|
+
|
|
68
|
+
# Notifications and audit
|
|
69
|
+
'notification_history': await self._get_notifications(user_id),
|
|
70
|
+
'audit_logs': await self._get_audit_logs(user_id),
|
|
71
|
+
|
|
72
|
+
# Chaos experiments
|
|
73
|
+
'chaos_experiments': await self._get_chaos_experiments(user_id),
|
|
74
|
+
|
|
75
|
+
# Third party data sharing disclosure
|
|
76
|
+
'third_party_processors': [
|
|
77
|
+
{
|
|
78
|
+
'name': 'Supabase',
|
|
79
|
+
'purpose': 'Database hosting and authentication',
|
|
80
|
+
'location': 'United States (AWS us-east-1)',
|
|
81
|
+
'data_shared': 'All account data, services, incidents',
|
|
82
|
+
'data_retention': 'Until account deletion'
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
'name': 'OpenRouter/Google AI',
|
|
86
|
+
'purpose': 'AI analysis and postmortem generation',
|
|
87
|
+
'location': 'United States',
|
|
88
|
+
'data_shared': 'Incident descriptions, error messages (temporary)',
|
|
89
|
+
'data_retention': 'Not stored after processing'
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
'name': 'Stripe',
|
|
93
|
+
'purpose': 'Payment processing',
|
|
94
|
+
'location': 'United States',
|
|
95
|
+
'data_shared': 'Email, payment method',
|
|
96
|
+
'data_retention': 'Per Stripe policies'
|
|
97
|
+
}
|
|
98
|
+
]
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return export_data
|
|
102
|
+
|
|
103
|
+
async def delete_all_data(
|
|
104
|
+
self,
|
|
105
|
+
user_id: str,
|
|
106
|
+
confirmation: str
|
|
107
|
+
) -> Dict:
|
|
108
|
+
"""
|
|
109
|
+
GDPR Article 17 - Right to be forgotten
|
|
110
|
+
|
|
111
|
+
Permanently deletes ALL user data
|
|
112
|
+
"""
|
|
113
|
+
# Require explicit confirmation
|
|
114
|
+
if confirmation != "DELETE_MY_DATA":
|
|
115
|
+
raise ValueError(
|
|
116
|
+
"Must confirm deletion by providing confirmation='DELETE_MY_DATA'"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
deleted_counts = {}
|
|
120
|
+
|
|
121
|
+
# Order matters! Delete dependent records first
|
|
122
|
+
|
|
123
|
+
# 1. Delete AI usage logs
|
|
124
|
+
result = await self.supabase.table('ai_usage').delete().eq(
|
|
125
|
+
'user_id', user_id
|
|
126
|
+
).execute()
|
|
127
|
+
deleted_counts['ai_usage'] = len(result.data) if result.data else 0
|
|
128
|
+
|
|
129
|
+
# 2. Delete notification history
|
|
130
|
+
result = await self.supabase.table('notification_history').delete().eq(
|
|
131
|
+
'user_id', user_id
|
|
132
|
+
).execute()
|
|
133
|
+
deleted_counts['notifications'] = len(result.data) if result.data else 0
|
|
134
|
+
|
|
135
|
+
# 3. Delete rate limit logs
|
|
136
|
+
result = await self.supabase.table('rate_limit_log').delete().eq(
|
|
137
|
+
'user_id', user_id
|
|
138
|
+
).execute()
|
|
139
|
+
deleted_counts['rate_limits'] = len(result.data) if result.data else 0
|
|
140
|
+
|
|
141
|
+
# 4. Delete chaos experiments
|
|
142
|
+
result = await self.supabase.table('chaos_experiments').delete().eq(
|
|
143
|
+
'user_id', user_id
|
|
144
|
+
).execute()
|
|
145
|
+
deleted_counts['chaos_experiments'] = len(result.data) if result.data else 0
|
|
146
|
+
|
|
147
|
+
# 5. Delete runbook executions (via incidents)
|
|
148
|
+
# Will cascade from incidents
|
|
149
|
+
|
|
150
|
+
# 6. Delete incidents
|
|
151
|
+
result = await self.supabase.table('incidents').delete().eq(
|
|
152
|
+
'user_id', user_id
|
|
153
|
+
).execute()
|
|
154
|
+
deleted_counts['incidents'] = len(result.data) if result.data else 0
|
|
155
|
+
|
|
156
|
+
# 7. Delete health checks
|
|
157
|
+
result = await self.supabase.table('health_checks').delete().eq(
|
|
158
|
+
'user_id', user_id
|
|
159
|
+
).execute()
|
|
160
|
+
deleted_counts['health_checks'] = len(result.data) if result.data else 0
|
|
161
|
+
|
|
162
|
+
# 8. Delete services
|
|
163
|
+
result = await self.supabase.table('services').delete().eq(
|
|
164
|
+
'user_id', user_id
|
|
165
|
+
).execute()
|
|
166
|
+
deleted_counts['services'] = len(result.data) if result.data else 0
|
|
167
|
+
|
|
168
|
+
# 9. Delete runbooks
|
|
169
|
+
result = await self.supabase.table('runbooks').delete().eq(
|
|
170
|
+
'user_id', user_id
|
|
171
|
+
).execute()
|
|
172
|
+
deleted_counts['runbooks'] = len(result.data) if result.data else 0
|
|
173
|
+
|
|
174
|
+
# 10. Delete team memberships
|
|
175
|
+
result = await self.supabase.table('team_members').delete().eq(
|
|
176
|
+
'user_id', user_id
|
|
177
|
+
).execute()
|
|
178
|
+
deleted_counts['team_members'] = len(result.data) if result.data else 0
|
|
179
|
+
|
|
180
|
+
# 11. Delete user account (last!)
|
|
181
|
+
result = await self.supabase.table('users').delete().eq(
|
|
182
|
+
'id', user_id
|
|
183
|
+
).execute()
|
|
184
|
+
deleted_counts['user'] = 1 if result.data else 0
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
'status': 'deleted',
|
|
188
|
+
'user_id': user_id,
|
|
189
|
+
'deleted_at': datetime.utcnow().isoformat(),
|
|
190
|
+
'deleted_counts': deleted_counts,
|
|
191
|
+
'message': 'All personal data has been permanently deleted'
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async def get_data_usage_summary(self, user_id: str) -> Dict:
|
|
195
|
+
"""
|
|
196
|
+
Transparency endpoint - show what data we have
|
|
197
|
+
"""
|
|
198
|
+
account = await self._get_account_data(user_id)
|
|
199
|
+
|
|
200
|
+
# Count records in each table
|
|
201
|
+
services = await self.supabase.table('services').select(
|
|
202
|
+
'id', count='exact'
|
|
203
|
+
).eq('user_id', user_id).execute()
|
|
204
|
+
|
|
205
|
+
incidents = await self.supabase.table('incidents').select(
|
|
206
|
+
'id', count='exact'
|
|
207
|
+
).eq('user_id', user_id).execute()
|
|
208
|
+
|
|
209
|
+
ai_usage = await self.supabase.table('ai_usage').select(
|
|
210
|
+
'cost_usd'
|
|
211
|
+
).eq('user_id', user_id).execute()
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
'account_created': account.get('created_at'),
|
|
215
|
+
'email': account.get('email'),
|
|
216
|
+
'subscription_tier': account.get('subscription_tier', 'free'),
|
|
217
|
+
|
|
218
|
+
'data_summary': {
|
|
219
|
+
'services_monitored': services.count if hasattr(services, 'count') else len(services.data),
|
|
220
|
+
'incidents_tracked': incidents.count if hasattr(incidents, 'count') else len(incidents.data),
|
|
221
|
+
'ai_calls_made': len(ai_usage.data) if ai_usage.data else 0,
|
|
222
|
+
'total_ai_cost': sum(r.get('cost_usd', 0) for r in (ai_usage.data or []))
|
|
223
|
+
},
|
|
224
|
+
|
|
225
|
+
'data_sharing': {
|
|
226
|
+
'third_parties': ['Supabase (database)', 'OpenRouter (AI)', 'Stripe (payments)'],
|
|
227
|
+
'data_shared_with_ai': 'Incident descriptions only (not stored)',
|
|
228
|
+
'data_sold': 'Never'
|
|
229
|
+
},
|
|
230
|
+
|
|
231
|
+
'your_rights': {
|
|
232
|
+
'export_data': 'POST /api/user/export-data',
|
|
233
|
+
'delete_account': 'DELETE /api/user/delete-account',
|
|
234
|
+
'update_preferences': 'PATCH /api/user/preferences'
|
|
235
|
+
},
|
|
236
|
+
|
|
237
|
+
'retention_policy': {
|
|
238
|
+
'incidents': '2 years',
|
|
239
|
+
'health_checks': '90 days',
|
|
240
|
+
'audit_logs': '90 days',
|
|
241
|
+
'ai_usage_logs': '1 year'
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
# Helper methods to fetch data
|
|
246
|
+
|
|
247
|
+
async def _get_account_data(self, user_id: str) -> Dict:
|
|
248
|
+
result = await self.supabase.table('users').select('*').eq(
|
|
249
|
+
'id', user_id
|
|
250
|
+
).execute()
|
|
251
|
+
|
|
252
|
+
if result.data:
|
|
253
|
+
user = result.data[0]
|
|
254
|
+
# Remove sensitive fields
|
|
255
|
+
user.pop('password_hash', None)
|
|
256
|
+
return user
|
|
257
|
+
return {}
|
|
258
|
+
|
|
259
|
+
async def _get_services(self, user_id: str):
|
|
260
|
+
result = await self.supabase.table('services').select('*').eq(
|
|
261
|
+
'user_id', user_id
|
|
262
|
+
).execute()
|
|
263
|
+
return result.data or []
|
|
264
|
+
|
|
265
|
+
async def _get_health_checks(self, user_id: str):
|
|
266
|
+
# Get health checks for user's services
|
|
267
|
+
services = await self._get_services(user_id)
|
|
268
|
+
service_ids = [s['id'] for s in services]
|
|
269
|
+
|
|
270
|
+
if not service_ids:
|
|
271
|
+
return []
|
|
272
|
+
|
|
273
|
+
result = await self.supabase.table('health_checks').select('*').in_(
|
|
274
|
+
'service_id', service_ids
|
|
275
|
+
).limit(1000).execute() # Limit for performance
|
|
276
|
+
|
|
277
|
+
return result.data or []
|
|
278
|
+
|
|
279
|
+
async def _get_incidents(self, user_id: str):
|
|
280
|
+
result = await self.supabase.table('incidents').select('*').eq(
|
|
281
|
+
'user_id', user_id
|
|
282
|
+
).execute()
|
|
283
|
+
return result.data or []
|
|
284
|
+
|
|
285
|
+
async def _get_postmortems(self, user_id: str):
|
|
286
|
+
result = await self.supabase.table('postmortems').select('*').eq(
|
|
287
|
+
'user_id', user_id
|
|
288
|
+
).execute()
|
|
289
|
+
return result.data or []
|
|
290
|
+
|
|
291
|
+
async def _get_runbooks(self, user_id: str):
|
|
292
|
+
result = await self.supabase.table('runbooks').select('*').eq(
|
|
293
|
+
'user_id', user_id
|
|
294
|
+
).execute()
|
|
295
|
+
return result.data or []
|
|
296
|
+
|
|
297
|
+
async def _get_runbook_executions(self, user_id: str):
|
|
298
|
+
result = await self.supabase.table('runbook_executions').select('*').eq(
|
|
299
|
+
'executed_by', user_id
|
|
300
|
+
).execute()
|
|
301
|
+
return result.data or []
|
|
302
|
+
|
|
303
|
+
async def _get_team_members(self, user_id: str):
|
|
304
|
+
result = await self.supabase.table('team_members').select('*').eq(
|
|
305
|
+
'user_id', user_id
|
|
306
|
+
).execute()
|
|
307
|
+
return result.data or []
|
|
308
|
+
|
|
309
|
+
async def _get_on_call_schedules(self, user_id: str):
|
|
310
|
+
result = await self.supabase.table('on_call_schedules').select('*').eq(
|
|
311
|
+
'user_id', user_id
|
|
312
|
+
).execute()
|
|
313
|
+
return result.data or []
|
|
314
|
+
|
|
315
|
+
async def _get_embeddings(self, user_id: str):
|
|
316
|
+
"""Get vector embeddings (GDPR requires this!)"""
|
|
317
|
+
incidents = await self._get_incidents(user_id)
|
|
318
|
+
|
|
319
|
+
# Return embedding metadata, not raw vectors (too large)
|
|
320
|
+
embeddings = []
|
|
321
|
+
for inc in incidents:
|
|
322
|
+
if inc.get('embedding'):
|
|
323
|
+
embeddings.append({
|
|
324
|
+
'incident_id': inc['id'],
|
|
325
|
+
'embedding_model': 'text-embedding-3-small',
|
|
326
|
+
'embedding_dimensions': 1536,
|
|
327
|
+
'created_at': inc.get('created_at')
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
return embeddings
|
|
331
|
+
|
|
332
|
+
async def _get_ai_usage(self, user_id: str):
|
|
333
|
+
result = await self.supabase.table('ai_usage').select('*').eq(
|
|
334
|
+
'user_id', user_id
|
|
335
|
+
).execute()
|
|
336
|
+
return result.data or []
|
|
337
|
+
|
|
338
|
+
async def _get_notifications(self, user_id: str):
|
|
339
|
+
result = await self.supabase.table('notification_history').select('*').eq(
|
|
340
|
+
'user_id', user_id
|
|
341
|
+
).execute()
|
|
342
|
+
return result.data or []
|
|
343
|
+
|
|
344
|
+
async def _get_audit_logs(self, user_id: str):
|
|
345
|
+
result = await self.supabase.table('audit_logs').select('*').eq(
|
|
346
|
+
'user_id', user_id
|
|
347
|
+
).limit(1000).execute()
|
|
348
|
+
return result.data or []
|
|
349
|
+
|
|
350
|
+
async def _get_chaos_experiments(self, user_id: str):
|
|
351
|
+
result = await self.supabase.table('chaos_experiments').select('*').eq(
|
|
352
|
+
'user_id', user_id
|
|
353
|
+
).execute()
|
|
354
|
+
return result.data or []
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
# FastAPI Routes
|
|
358
|
+
|
|
359
|
+
@router.post("/export-data")
|
|
360
|
+
async def export_user_data(
|
|
361
|
+
user_id: str,
|
|
362
|
+
supabase=Depends(lambda: None) # Replace with actual dependency
|
|
363
|
+
):
|
|
364
|
+
"""GDPR Article 20 - Export all personal data"""
|
|
365
|
+
handler = GDPRHandler(supabase)
|
|
366
|
+
data = await handler.export_all_data(user_id)
|
|
367
|
+
|
|
368
|
+
filename = f"devops-sentinel-export-{user_id}-{datetime.utcnow().strftime('%Y%m%d')}.json"
|
|
369
|
+
|
|
370
|
+
return JSONResponse(
|
|
371
|
+
content=data,
|
|
372
|
+
headers={
|
|
373
|
+
'Content-Disposition': f'attachment; filename={filename}'
|
|
374
|
+
}
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
@router.delete("/delete-account")
|
|
379
|
+
async def delete_user_account(
|
|
380
|
+
user_id: str,
|
|
381
|
+
confirmation: str,
|
|
382
|
+
supabase=Depends(lambda: None)
|
|
383
|
+
):
|
|
384
|
+
"""GDPR Article 17 - Right to be forgotten"""
|
|
385
|
+
if confirmation != "DELETE_MY_DATA":
|
|
386
|
+
raise HTTPException(
|
|
387
|
+
status_code=400,
|
|
388
|
+
detail="Must provide confirmation='DELETE_MY_DATA' to delete account"
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
handler = GDPRHandler(supabase)
|
|
392
|
+
result = await handler.delete_all_data(user_id, confirmation)
|
|
393
|
+
|
|
394
|
+
return result
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
@router.get("/data-usage")
|
|
398
|
+
async def get_data_usage(
|
|
399
|
+
user_id: str,
|
|
400
|
+
supabase=Depends(lambda: None)
|
|
401
|
+
):
|
|
402
|
+
"""Transparency - show what data we have on user"""
|
|
403
|
+
handler = GDPRHandler(supabase)
|
|
404
|
+
return await handler.get_data_usage_summary(user_id)
|