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.
Files changed (51) hide show
  1. devops_sentinel-0.1.0.dist-info/METADATA +185 -0
  2. devops_sentinel-0.1.0.dist-info/RECORD +51 -0
  3. devops_sentinel-0.1.0.dist-info/WHEEL +5 -0
  4. devops_sentinel-0.1.0.dist-info/entry_points.txt +2 -0
  5. devops_sentinel-0.1.0.dist-info/top_level.txt +1 -0
  6. src/__init__.py +3 -0
  7. src/api/gdpr_endpoints.py +404 -0
  8. src/api/quick_health_check.py +296 -0
  9. src/api/waitlist.py +166 -0
  10. src/auth/auth_helper.py +198 -0
  11. src/auth/auth_service.py +431 -0
  12. src/cli/__init__.py +5 -0
  13. src/cli/auth.py +456 -0
  14. src/cli/db.py +212 -0
  15. src/cli/main.py +436 -0
  16. src/cli/projects.py +109 -0
  17. src/cli/services.py +164 -0
  18. src/core/__init__.py +30 -0
  19. src/core/alert_explainer.py +370 -0
  20. src/core/alert_router.py +300 -0
  21. src/core/anomaly_detector.py +375 -0
  22. src/core/auto_runbook_executor.py +492 -0
  23. src/core/baseline_monitor.py +301 -0
  24. src/core/byok_tier.py +168 -0
  25. src/core/classifier.py +275 -0
  26. src/core/cost_tracker.py +324 -0
  27. src/core/dependency_analyzer.py +388 -0
  28. src/core/deployment_correlator.py +401 -0
  29. src/core/incident_memory.py +436 -0
  30. src/core/on_call_manager.py +407 -0
  31. src/core/postmortem_generator.py +373 -0
  32. src/core/rate_limiter.py +337 -0
  33. src/core/runbook_matcher.py +416 -0
  34. src/core/service_map_generator.py +395 -0
  35. src/core/team_assignment_system.py +368 -0
  36. src/integrations/email_notification_system.py +464 -0
  37. src/integrations/github_oauth.py +168 -0
  38. src/integrations/integrations_hub.py +182 -0
  39. src/integrations/pagerduty_integration.py +314 -0
  40. src/integrations/pagerduty_oauth.py +162 -0
  41. src/integrations/slack.py +458 -0
  42. src/integrations/slack_integration.py +425 -0
  43. src/integrations/slack_oauth.py +295 -0
  44. src/ml/__init__.py +10 -0
  45. src/ml/anomaly_detector.py +336 -0
  46. src/notifications/email_templates.py +508 -0
  47. src/setup/ai_setup.py +187 -0
  48. src/setup/supabase_setup.py +295 -0
  49. src/tools/chaos_engineering_tools.py +448 -0
  50. src/tools/custom_health_check_tool.py +317 -0
  51. 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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.10.2)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ sentinel = src.cli.main:cli
@@ -0,0 +1 @@
1
+ src
src/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """DevOps Sentinel - Autonomous SRE Agents"""
2
+
3
+ __version__ = "0.1.0"
@@ -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)