devtorch-core 3.0.1__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.
- devtorch_core/__init__.py +158 -0
- devtorch_core/aggphi_textual.py +275 -0
- devtorch_core/alerts/__init__.py +23 -0
- devtorch_core/alerts/base.py +46 -0
- devtorch_core/alerts/config.py +60 -0
- devtorch_core/alerts/dispatcher.py +110 -0
- devtorch_core/alerts/jira.py +96 -0
- devtorch_core/alerts/linear.py +72 -0
- devtorch_core/alerts/pagerduty.py +66 -0
- devtorch_core/alerts/slack.py +81 -0
- devtorch_core/alerts/teams.py +70 -0
- devtorch_core/audit/__init__.py +43 -0
- devtorch_core/audit/exporter.py +297 -0
- devtorch_core/audit/privacy.py +101 -0
- devtorch_core/audit/scrubber.py +149 -0
- devtorch_core/audit/service.py +67 -0
- devtorch_core/audit/signing.py +127 -0
- devtorch_core/broadcast/__init__.py +4 -0
- devtorch_core/broadcast/broadcaster.py +100 -0
- devtorch_core/broadcast/watcher.py +71 -0
- devtorch_core/capability.py +639 -0
- devtorch_core/cloud/__init__.py +1 -0
- devtorch_core/cloud/client_config.py +472 -0
- devtorch_core/cloud/client_configs/.claude-opencode-fallback.json +8 -0
- devtorch_core/cloud/client_configs/.claude-stdio.json +13 -0
- devtorch_core/cloud/client_configs/.cursor-mcp.json +13 -0
- devtorch_core/cloud/client_configs/.opencode-bridge.json +13 -0
- devtorch_core/cloud/client_configs/.opencode.json +15 -0
- devtorch_core/cloud/client_configs/.vscode-mcp.json +13 -0
- devtorch_core/cloud/devtorch-mcp-bridge.js +357 -0
- devtorch_core/cloud/mcp_client.py +229 -0
- devtorch_core/cloud/setup.py +144 -0
- devtorch_core/cloud/sync.py +143 -0
- devtorch_core/cloud/sync_bundle.py +603 -0
- devtorch_core/cloud/sync_conflicts.py +159 -0
- devtorch_core/cloud/sync_state.py +159 -0
- devtorch_core/cloud/team_sync.py +283 -0
- devtorch_core/codex/__init__.py +9 -0
- devtorch_core/codex/__main__.py +97 -0
- devtorch_core/codex/capture.py +208 -0
- devtorch_core/codex/proxy.py +412 -0
- devtorch_core/concept_catalog.py +209 -0
- devtorch_core/consolidation/__init__.py +3 -0
- devtorch_core/consolidation/synthesizer.py +87 -0
- devtorch_core/consolidation/workflow.py +175 -0
- devtorch_core/daemon/__init__.py +27 -0
- devtorch_core/daemon/supervisor.py +293 -0
- devtorch_core/daemon/watcher.py +244 -0
- devtorch_core/dashboard_api.py +2012 -0
- devtorch_core/deltaf.py +97 -0
- devtorch_core/disclosure.py +50 -0
- devtorch_core/divergence/__init__.py +3 -0
- devtorch_core/divergence/detector.py +166 -0
- devtorch_core/gateway/__init__.py +32 -0
- devtorch_core/gateway/key_manager.py +124 -0
- devtorch_core/gateway/metrics_webhook.py +252 -0
- devtorch_core/gateway/policy.py +262 -0
- devtorch_core/gateway/server.py +727 -0
- devtorch_core/gateway/sso.py +233 -0
- devtorch_core/gcc.py +1246 -0
- devtorch_core/github/__init__.py +35 -0
- devtorch_core/github/app.py +240 -0
- devtorch_core/github/comment_builder.py +113 -0
- devtorch_core/github/pat.py +76 -0
- devtorch_core/github/pr_parser.py +82 -0
- devtorch_core/github/pr_reporter.py +555 -0
- devtorch_core/gitlab/__init__.py +177 -0
- devtorch_core/hitl/__init__.py +4 -0
- devtorch_core/hitl/channels.py +129 -0
- devtorch_core/hitl/orchestrator.py +95 -0
- devtorch_core/hooks/__init__.py +17 -0
- devtorch_core/hooks/claude_code.py +228 -0
- devtorch_core/hooks/git_capture.py +341 -0
- devtorch_core/hooks/git_commit.py +182 -0
- devtorch_core/hooks/installer.py +733 -0
- devtorch_core/hooks/pre_commit.py +157 -0
- devtorch_core/hooks/runner.py +344 -0
- devtorch_core/identity/__init__.py +4 -0
- devtorch_core/identity/agent.py +86 -0
- devtorch_core/identity/providers.py +85 -0
- devtorch_core/invariants.py +182 -0
- devtorch_core/mcp/__init__.py +10 -0
- devtorch_core/mcp/auth.py +177 -0
- devtorch_core/mcp/server.py +1049 -0
- devtorch_core/metrics/__init__.py +35 -0
- devtorch_core/metrics/aggregate.py +215 -0
- devtorch_core/metrics/calibrate.py +198 -0
- devtorch_core/metrics/calibration.py +125 -0
- devtorch_core/metrics/credibility.py +288 -0
- devtorch_core/metrics/delivery_time.py +70 -0
- devtorch_core/metrics/dhs.py +126 -0
- devtorch_core/metrics/mcs.py +96 -0
- devtorch_core/metrics/roi.py +88 -0
- devtorch_core/metrics/session_writer.py +81 -0
- devtorch_core/metrics/shadow_ai.py +117 -0
- devtorch_core/metrics/sprint_writer.py +243 -0
- devtorch_core/observability/__init__.py +78 -0
- devtorch_core/observability/datadog.py +157 -0
- devtorch_core/observability/formatter.py +119 -0
- devtorch_core/observability/report.py +264 -0
- devtorch_core/observability/servicenow.py +147 -0
- devtorch_core/observability/splunk.py +218 -0
- devtorch_core/observability/webhook.py +227 -0
- devtorch_core/parser/__init__.py +30 -0
- devtorch_core/parser/blocks.py +216 -0
- devtorch_core/parser/inference.py +159 -0
- devtorch_core/parser/thinking.py +112 -0
- devtorch_core/projects.py +169 -0
- devtorch_core/prompt_artifact.py +76 -0
- devtorch_core/proxy/__init__.py +9 -0
- devtorch_core/proxy/routes/__init__.py +1 -0
- devtorch_core/proxy/routes/anthropic.py +264 -0
- devtorch_core/proxy/routes/azure_openai.py +336 -0
- devtorch_core/proxy/routes/gemini.py +331 -0
- devtorch_core/proxy/routes/groq.py +284 -0
- devtorch_core/proxy/routes/ollama.py +279 -0
- devtorch_core/proxy/routes/openai.py +287 -0
- devtorch_core/proxy/server.py +356 -0
- devtorch_core/query/__init__.py +15 -0
- devtorch_core/query/grep.py +181 -0
- devtorch_core/query/hybrid.py +86 -0
- devtorch_core/query/semantic.py +157 -0
- devtorch_core/rdp.py +105 -0
- devtorch_core/reasoning/__init__.py +4 -0
- devtorch_core/reasoning/entry.py +31 -0
- devtorch_core/reasoning/store.py +122 -0
- devtorch_core/reasoning_plus/__init__.py +70 -0
- devtorch_core/reasoning_plus/augmenter.py +326 -0
- devtorch_core/reasoning_plus/capture.py +51 -0
- devtorch_core/reasoning_plus/config.py +256 -0
- devtorch_core/reasoning_plus/context.py +262 -0
- devtorch_core/reasoning_plus/learning/__init__.py +72 -0
- devtorch_core/reasoning_plus/learning/analytics.py +141 -0
- devtorch_core/reasoning_plus/learning/api.py +313 -0
- devtorch_core/reasoning_plus/learning/chain.py +285 -0
- devtorch_core/reasoning_plus/learning/composer.py +74 -0
- devtorch_core/reasoning_plus/learning/cross_project.py +234 -0
- devtorch_core/reasoning_plus/learning/embeddings.py +209 -0
- devtorch_core/reasoning_plus/learning/extractor.py +207 -0
- devtorch_core/reasoning_plus/learning/models.py +116 -0
- devtorch_core/reasoning_plus/learning/provenance.py +126 -0
- devtorch_core/reasoning_plus/learning/recorder.py +81 -0
- devtorch_core/reasoning_plus/learning/relevance.py +122 -0
- devtorch_core/reasoning_plus/learning/state.py +86 -0
- devtorch_core/reasoning_plus/learning/store.py +160 -0
- devtorch_core/reasoning_plus/learning/theta_learning_bridge.py +94 -0
- devtorch_core/reasoning_plus/prompt.py +90 -0
- devtorch_core/rep.py +134 -0
- devtorch_core/rep_network/__init__.py +25 -0
- devtorch_core/rep_network/merge.py +70 -0
- devtorch_core/rep_network/node.py +137 -0
- devtorch_core/rep_network/server.py +140 -0
- devtorch_core/rep_network/sync.py +207 -0
- devtorch_core/sensitivity.py +182 -0
- devtorch_core/serve.py +258 -0
- devtorch_core/session/__init__.py +39 -0
- devtorch_core/session/disagreement.py +188 -0
- devtorch_core/session/models.py +114 -0
- devtorch_core/session/orchestrator.py +182 -0
- devtorch_core/session/planner.py +169 -0
- devtorch_core/session/simulator.py +132 -0
- devtorch_core/signing.py +290 -0
- devtorch_core/sis.py +197 -0
- devtorch_core/storage.py +308 -0
- devtorch_core/templates/__init__.py +6 -0
- devtorch_core/templates/engine.py +122 -0
- devtorch_core/templates/go.py +18 -0
- devtorch_core/templates/infra.py +19 -0
- devtorch_core/templates/library/__init__.py +18 -0
- devtorch_core/templates/library/api_design.md +27 -0
- devtorch_core/templates/library/bug_fix.md +27 -0
- devtorch_core/templates/library/decision_record.md +27 -0
- devtorch_core/templates/library/engine.py +228 -0
- devtorch_core/templates/library/security_review.md +30 -0
- devtorch_core/templates/python.py +19 -0
- devtorch_core/templates/react.py +18 -0
- devtorch_core/templates/typescript.py +18 -0
- devtorch_core/theta.py +221 -0
- devtorch_core/theta_synthesis.py +268 -0
- devtorch_core/topics.py +320 -0
- devtorch_core/variance.py +219 -0
- devtorch_core/wrapper/__init__.py +52 -0
- devtorch_core/wrapper/anthropic.py +487 -0
- devtorch_core/wrapper/base.py +562 -0
- devtorch_core/wrapper/bedrock.py +342 -0
- devtorch_core/wrapper/gemini.py +422 -0
- devtorch_core/wrapper/ollama.py +527 -0
- devtorch_core/wrapper/openai.py +461 -0
- devtorch_core-3.0.1.dist-info/METADATA +867 -0
- devtorch_core-3.0.1.dist-info/RECORD +193 -0
- devtorch_core-3.0.1.dist-info/WHEEL +5 -0
- devtorch_core-3.0.1.dist-info/entry_points.txt +2 -0
- devtorch_core-3.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import json
|
|
3
|
+
import urllib.request
|
|
4
|
+
import urllib.error
|
|
5
|
+
|
|
6
|
+
from .base import AlertChannel, AlertEnvelope, AlertSeverity, DeliveryResult
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class JiraChannel(AlertChannel):
|
|
10
|
+
channel_name = "jira"
|
|
11
|
+
|
|
12
|
+
def __init__(self, url: str, project_key: str, api_token: str, email: str) -> None:
|
|
13
|
+
self.url = url.rstrip("/")
|
|
14
|
+
self.project_key = project_key
|
|
15
|
+
self.api_token = api_token
|
|
16
|
+
self.email = email
|
|
17
|
+
|
|
18
|
+
def _build_issue_body(self, envelope: AlertEnvelope) -> dict:
|
|
19
|
+
"""Build Jira issue creation request body."""
|
|
20
|
+
issue_type = "Bug" if envelope.severity == AlertSeverity.CRITICAL else "Task"
|
|
21
|
+
summary = f"[DevTorch] {envelope.title}"
|
|
22
|
+
|
|
23
|
+
# ADF (Atlassian Document Format) — plain text wrapped in doc node
|
|
24
|
+
description_adf = {
|
|
25
|
+
"version": 1,
|
|
26
|
+
"type": "doc",
|
|
27
|
+
"content": [
|
|
28
|
+
{
|
|
29
|
+
"type": "paragraph",
|
|
30
|
+
"content": [
|
|
31
|
+
{
|
|
32
|
+
"type": "text",
|
|
33
|
+
"text": envelope.body,
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"type": "paragraph",
|
|
39
|
+
"content": [
|
|
40
|
+
{
|
|
41
|
+
"type": "text",
|
|
42
|
+
"text": f"Branch: {envelope.branch} | Commit: {envelope.commit_id} | Event: {envelope.event_type}",
|
|
43
|
+
}
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
"fields": {
|
|
51
|
+
"project": {"key": self.project_key},
|
|
52
|
+
"summary": summary,
|
|
53
|
+
"description": description_adf,
|
|
54
|
+
"issuetype": {"name": issue_type},
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
def deliver(self, envelope: AlertEnvelope) -> DeliveryResult:
|
|
59
|
+
"""POST to Jira REST API v3. Basic auth with email:token."""
|
|
60
|
+
try:
|
|
61
|
+
body = self._build_issue_body(envelope)
|
|
62
|
+
data = json.dumps(body).encode("utf-8")
|
|
63
|
+
|
|
64
|
+
credentials = f"{self.email}:{self.api_token}"
|
|
65
|
+
encoded = base64.b64encode(credentials.encode("utf-8")).decode("utf-8")
|
|
66
|
+
|
|
67
|
+
api_url = f"{self.url}/rest/api/3/issue"
|
|
68
|
+
req = urllib.request.Request(
|
|
69
|
+
api_url,
|
|
70
|
+
data=data,
|
|
71
|
+
headers={
|
|
72
|
+
"Content-Type": "application/json",
|
|
73
|
+
"Authorization": f"Basic {encoded}",
|
|
74
|
+
"Accept": "application/json",
|
|
75
|
+
},
|
|
76
|
+
method="POST",
|
|
77
|
+
)
|
|
78
|
+
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
79
|
+
return DeliveryResult(
|
|
80
|
+
channel=self.channel_name,
|
|
81
|
+
success=True,
|
|
82
|
+
response_code=resp.status,
|
|
83
|
+
)
|
|
84
|
+
except urllib.error.HTTPError as exc:
|
|
85
|
+
return DeliveryResult(
|
|
86
|
+
channel=self.channel_name,
|
|
87
|
+
success=False,
|
|
88
|
+
error=f"HTTP {exc.code}: {exc.reason}",
|
|
89
|
+
response_code=exc.code,
|
|
90
|
+
)
|
|
91
|
+
except Exception as exc:
|
|
92
|
+
return DeliveryResult(
|
|
93
|
+
channel=self.channel_name,
|
|
94
|
+
success=False,
|
|
95
|
+
error=str(exc),
|
|
96
|
+
)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import urllib.request
|
|
3
|
+
import urllib.error
|
|
4
|
+
|
|
5
|
+
from .base import AlertChannel, AlertEnvelope, AlertSeverity, DeliveryResult
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class LinearChannel(AlertChannel):
|
|
9
|
+
channel_name = "linear"
|
|
10
|
+
|
|
11
|
+
def __init__(self, api_token: str, team_id: str) -> None:
|
|
12
|
+
self.api_token = api_token
|
|
13
|
+
self.team_id = team_id
|
|
14
|
+
|
|
15
|
+
def _build_issue_input(self, envelope: AlertEnvelope) -> dict:
|
|
16
|
+
"""Build Linear GraphQL issue create input."""
|
|
17
|
+
priority_map = {
|
|
18
|
+
AlertSeverity.CRITICAL: 1,
|
|
19
|
+
AlertSeverity.WARNING: 2,
|
|
20
|
+
AlertSeverity.INFO: 3,
|
|
21
|
+
}
|
|
22
|
+
title = f"[DevTorch] {envelope.title}"
|
|
23
|
+
description = (
|
|
24
|
+
f"{envelope.body}\n\n"
|
|
25
|
+
f"Event: {envelope.event_type}\n"
|
|
26
|
+
f"Branch: {envelope.branch}\n"
|
|
27
|
+
f"Commit: {envelope.commit_id}"
|
|
28
|
+
)
|
|
29
|
+
return {
|
|
30
|
+
"title": title,
|
|
31
|
+
"description": description,
|
|
32
|
+
"teamId": self.team_id,
|
|
33
|
+
"priority": priority_map.get(envelope.severity, 3),
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
def deliver(self, envelope: AlertEnvelope) -> DeliveryResult:
|
|
37
|
+
"""Create a Linear issue via GraphQL API."""
|
|
38
|
+
try:
|
|
39
|
+
issue = self._build_issue_input(envelope)
|
|
40
|
+
query = {
|
|
41
|
+
"query": "mutation IssueCreate($input: IssueCreateInput!) { issueCreate(input: $input) { success issue { id identifier } } }",
|
|
42
|
+
"variables": {"input": issue},
|
|
43
|
+
}
|
|
44
|
+
data = json.dumps(query).encode("utf-8")
|
|
45
|
+
req = urllib.request.Request(
|
|
46
|
+
"https://api.linear.app/graphql",
|
|
47
|
+
data=data,
|
|
48
|
+
headers={
|
|
49
|
+
"Content-Type": "application/json",
|
|
50
|
+
"Authorization": self.api_token,
|
|
51
|
+
},
|
|
52
|
+
method="POST",
|
|
53
|
+
)
|
|
54
|
+
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
55
|
+
return DeliveryResult(
|
|
56
|
+
channel=self.channel_name,
|
|
57
|
+
success=True,
|
|
58
|
+
response_code=resp.status,
|
|
59
|
+
)
|
|
60
|
+
except urllib.error.HTTPError as exc:
|
|
61
|
+
return DeliveryResult(
|
|
62
|
+
channel=self.channel_name,
|
|
63
|
+
success=False,
|
|
64
|
+
error=f"HTTP {exc.code}: {exc.reason}",
|
|
65
|
+
response_code=exc.code,
|
|
66
|
+
)
|
|
67
|
+
except Exception as exc:
|
|
68
|
+
return DeliveryResult(
|
|
69
|
+
channel=self.channel_name,
|
|
70
|
+
success=False,
|
|
71
|
+
error=str(exc),
|
|
72
|
+
)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import urllib.request
|
|
3
|
+
import urllib.error
|
|
4
|
+
|
|
5
|
+
from .base import AlertChannel, AlertEnvelope, AlertSeverity, DeliveryResult
|
|
6
|
+
|
|
7
|
+
_PD_SEVERITY_MAP = {
|
|
8
|
+
AlertSeverity.CRITICAL: "critical",
|
|
9
|
+
AlertSeverity.WARNING: "warning",
|
|
10
|
+
AlertSeverity.INFO: "info",
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class PagerDutyChannel(AlertChannel):
|
|
15
|
+
channel_name = "pagerduty"
|
|
16
|
+
EVENTS_API_URL = "https://events.pagerduty.com/v2/enqueue"
|
|
17
|
+
|
|
18
|
+
def __init__(self, routing_key: str) -> None:
|
|
19
|
+
self.routing_key = routing_key
|
|
20
|
+
|
|
21
|
+
def _build_payload(self, envelope: AlertEnvelope) -> dict:
|
|
22
|
+
"""Build PagerDuty Events API v2 payload."""
|
|
23
|
+
event_action = "trigger" if envelope.severity == AlertSeverity.CRITICAL else "acknowledge"
|
|
24
|
+
pd_severity = _PD_SEVERITY_MAP.get(envelope.severity, "info")
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
"routing_key": self.routing_key,
|
|
28
|
+
"event_action": event_action,
|
|
29
|
+
"payload": {
|
|
30
|
+
"summary": envelope.title,
|
|
31
|
+
"severity": pd_severity,
|
|
32
|
+
"source": "devtorch",
|
|
33
|
+
"custom_details": envelope.metadata,
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
def deliver(self, envelope: AlertEnvelope) -> DeliveryResult:
|
|
38
|
+
"""POST to PagerDuty Events API. Never raises."""
|
|
39
|
+
try:
|
|
40
|
+
payload = self._build_payload(envelope)
|
|
41
|
+
data = json.dumps(payload).encode("utf-8")
|
|
42
|
+
req = urllib.request.Request(
|
|
43
|
+
self.EVENTS_API_URL,
|
|
44
|
+
data=data,
|
|
45
|
+
headers={"Content-Type": "application/json"},
|
|
46
|
+
method="POST",
|
|
47
|
+
)
|
|
48
|
+
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
49
|
+
return DeliveryResult(
|
|
50
|
+
channel=self.channel_name,
|
|
51
|
+
success=True,
|
|
52
|
+
response_code=resp.status,
|
|
53
|
+
)
|
|
54
|
+
except urllib.error.HTTPError as exc:
|
|
55
|
+
return DeliveryResult(
|
|
56
|
+
channel=self.channel_name,
|
|
57
|
+
success=False,
|
|
58
|
+
error=f"HTTP {exc.code}: {exc.reason}",
|
|
59
|
+
response_code=exc.code,
|
|
60
|
+
)
|
|
61
|
+
except Exception as exc:
|
|
62
|
+
return DeliveryResult(
|
|
63
|
+
channel=self.channel_name,
|
|
64
|
+
success=False,
|
|
65
|
+
error=str(exc),
|
|
66
|
+
)
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import urllib.request
|
|
3
|
+
import urllib.error
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from .base import AlertChannel, AlertEnvelope, AlertSeverity, DeliveryResult
|
|
7
|
+
|
|
8
|
+
_SEVERITY_EMOJI = {
|
|
9
|
+
AlertSeverity.CRITICAL: "🔴",
|
|
10
|
+
AlertSeverity.WARNING: "🟡",
|
|
11
|
+
AlertSeverity.INFO: "🔵",
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SlackChannel(AlertChannel):
|
|
16
|
+
channel_name = "slack"
|
|
17
|
+
|
|
18
|
+
def __init__(self, webhook_url: str) -> None:
|
|
19
|
+
self.webhook_url = webhook_url
|
|
20
|
+
|
|
21
|
+
def _build_payload(self, envelope: AlertEnvelope) -> dict:
|
|
22
|
+
"""Build Slack Block Kit payload."""
|
|
23
|
+
emoji = _SEVERITY_EMOJI.get(envelope.severity, "🔵")
|
|
24
|
+
blocks: list[dict[str, Any]] = [
|
|
25
|
+
{
|
|
26
|
+
"type": "header",
|
|
27
|
+
"text": {
|
|
28
|
+
"type": "plain_text",
|
|
29
|
+
"text": f"{emoji} {envelope.severity.value.upper()}: {envelope.event_type}",
|
|
30
|
+
"emoji": True,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"type": "section",
|
|
35
|
+
"text": {
|
|
36
|
+
"type": "mrkdwn",
|
|
37
|
+
"text": f"*{envelope.title}*\n{envelope.body}",
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"type": "context",
|
|
42
|
+
"elements": [
|
|
43
|
+
{
|
|
44
|
+
"type": "mrkdwn",
|
|
45
|
+
"text": f"Branch: `{envelope.branch}` | Commit: `{envelope.commit_id}`",
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
]
|
|
50
|
+
return {"blocks": blocks}
|
|
51
|
+
|
|
52
|
+
def deliver(self, envelope: AlertEnvelope) -> DeliveryResult:
|
|
53
|
+
"""POST to Slack webhook. Uses urllib (no requests dependency)."""
|
|
54
|
+
try:
|
|
55
|
+
payload = self._build_payload(envelope)
|
|
56
|
+
data = json.dumps(payload).encode("utf-8")
|
|
57
|
+
req = urllib.request.Request(
|
|
58
|
+
self.webhook_url,
|
|
59
|
+
data=data,
|
|
60
|
+
headers={"Content-Type": "application/json"},
|
|
61
|
+
method="POST",
|
|
62
|
+
)
|
|
63
|
+
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
64
|
+
return DeliveryResult(
|
|
65
|
+
channel=self.channel_name,
|
|
66
|
+
success=True,
|
|
67
|
+
response_code=resp.status,
|
|
68
|
+
)
|
|
69
|
+
except urllib.error.HTTPError as exc:
|
|
70
|
+
return DeliveryResult(
|
|
71
|
+
channel=self.channel_name,
|
|
72
|
+
success=False,
|
|
73
|
+
error=f"HTTP {exc.code}: {exc.reason}",
|
|
74
|
+
response_code=exc.code,
|
|
75
|
+
)
|
|
76
|
+
except Exception as exc:
|
|
77
|
+
return DeliveryResult(
|
|
78
|
+
channel=self.channel_name,
|
|
79
|
+
success=False,
|
|
80
|
+
error=str(exc),
|
|
81
|
+
)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import urllib.request
|
|
3
|
+
import urllib.error
|
|
4
|
+
|
|
5
|
+
from .base import AlertChannel, AlertEnvelope, AlertSeverity, DeliveryResult
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class MicrosoftTeamsChannel(AlertChannel):
|
|
9
|
+
channel_name = "teams"
|
|
10
|
+
|
|
11
|
+
def __init__(self, webhook_url: str) -> None:
|
|
12
|
+
self.webhook_url = webhook_url
|
|
13
|
+
|
|
14
|
+
def _build_payload(self, envelope: AlertEnvelope) -> dict:
|
|
15
|
+
"""Build a simple MessageCard payload for Microsoft Teams connectors."""
|
|
16
|
+
color = {
|
|
17
|
+
AlertSeverity.CRITICAL: "ff0000",
|
|
18
|
+
AlertSeverity.WARNING: "ffff00",
|
|
19
|
+
AlertSeverity.INFO: "0000ff",
|
|
20
|
+
}.get(envelope.severity, "0000ff")
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
"@type": "MessageCard",
|
|
24
|
+
"@context": "https://schema.org/extensions",
|
|
25
|
+
"themeColor": color,
|
|
26
|
+
"summary": f"{envelope.severity.value.upper()}: {envelope.title}",
|
|
27
|
+
"sections": [
|
|
28
|
+
{
|
|
29
|
+
"activityTitle": f"DevTorch {envelope.severity.value.upper()} alert",
|
|
30
|
+
"activitySubtitle": envelope.event_type,
|
|
31
|
+
"facts": [
|
|
32
|
+
{"name": "Title", "value": envelope.title},
|
|
33
|
+
{"name": "Branch", "value": envelope.branch},
|
|
34
|
+
{"name": "Commit", "value": envelope.commit_id},
|
|
35
|
+
],
|
|
36
|
+
"text": envelope.body,
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
def deliver(self, envelope: AlertEnvelope) -> DeliveryResult:
|
|
42
|
+
"""POST to Microsoft Teams webhook."""
|
|
43
|
+
try:
|
|
44
|
+
payload = self._build_payload(envelope)
|
|
45
|
+
data = json.dumps(payload).encode("utf-8")
|
|
46
|
+
req = urllib.request.Request(
|
|
47
|
+
self.webhook_url,
|
|
48
|
+
data=data,
|
|
49
|
+
headers={"Content-Type": "application/json"},
|
|
50
|
+
method="POST",
|
|
51
|
+
)
|
|
52
|
+
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
53
|
+
return DeliveryResult(
|
|
54
|
+
channel=self.channel_name,
|
|
55
|
+
success=True,
|
|
56
|
+
response_code=resp.status,
|
|
57
|
+
)
|
|
58
|
+
except urllib.error.HTTPError as exc:
|
|
59
|
+
return DeliveryResult(
|
|
60
|
+
channel=self.channel_name,
|
|
61
|
+
success=False,
|
|
62
|
+
error=f"HTTP {exc.code}: {exc.reason}",
|
|
63
|
+
response_code=exc.code,
|
|
64
|
+
)
|
|
65
|
+
except Exception as exc:
|
|
66
|
+
return DeliveryResult(
|
|
67
|
+
channel=self.channel_name,
|
|
68
|
+
success=False,
|
|
69
|
+
error=str(exc),
|
|
70
|
+
)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Sprint 30 — Trust, Compliance & Security audit primitives.
|
|
2
|
+
|
|
3
|
+
Provides:
|
|
4
|
+
|
|
5
|
+
* PII/secrets scrubbing (regex-based default, optional presidio).
|
|
6
|
+
* Privacy policy enforcement stored in `.GCC/privacy.json`.
|
|
7
|
+
* Exportable, filterable audit reports from `.GCC/events.log.jsonl`.
|
|
8
|
+
* Optional Ed25519 tamper-evident signing of exported JSON.
|
|
9
|
+
|
|
10
|
+
Design decisions
|
|
11
|
+
----------------
|
|
12
|
+
* Regex-based redaction is the default so the OSS core stays lightweight and
|
|
13
|
+
does not pull heavy ML models (spacy/presidio) unless explicitly requested.
|
|
14
|
+
* Privacy configuration is a simple JSON file under `.GCC/` so it is versioned
|
|
15
|
+
with the repository and discoverable by the CLI and MCP server.
|
|
16
|
+
* Export signing uses asymmetric Ed25519 (via optional ``cryptography``) so a
|
|
17
|
+
report can be verified by anyone holding the public key, without access to the
|
|
18
|
+
private signing key.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from .scrubber import Scrubber, RegexScrubber, make_scrubber
|
|
24
|
+
from .privacy import PrivacyConfig, load_privacy_config, save_privacy_config, apply_privacy_policy
|
|
25
|
+
from .exporter import AuditFilters, AuditExporter, export_audit
|
|
26
|
+
from .signing import ExportSigner, AuditSignatureError
|
|
27
|
+
from .service import AuditService
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"Scrubber",
|
|
31
|
+
"RegexScrubber",
|
|
32
|
+
"make_scrubber",
|
|
33
|
+
"PrivacyConfig",
|
|
34
|
+
"load_privacy_config",
|
|
35
|
+
"save_privacy_config",
|
|
36
|
+
"apply_privacy_policy",
|
|
37
|
+
"AuditFilters",
|
|
38
|
+
"AuditExporter",
|
|
39
|
+
"export_audit",
|
|
40
|
+
"ExportSigner",
|
|
41
|
+
"AuditSignatureError",
|
|
42
|
+
"AuditService",
|
|
43
|
+
]
|