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,233 @@
|
|
|
1
|
+
"""
|
|
2
|
+
devtorch_core.gateway.sso
|
|
3
|
+
==========================
|
|
4
|
+
SSO / JWT token validation for the enterprise gateway.
|
|
5
|
+
|
|
6
|
+
Supports:
|
|
7
|
+
"none" — pass-through; returns an anonymous identity.
|
|
8
|
+
"okta" — validates a signed JWT from Okta.
|
|
9
|
+
"azure-ad" — validates a signed JWT from Azure Active Directory.
|
|
10
|
+
"google" — validates a signed JWT from Google Workspace.
|
|
11
|
+
|
|
12
|
+
PyJWT is an optional dependency. If it is not installed the module falls back
|
|
13
|
+
to a base64 payload decode (no signature verification) and logs a clear
|
|
14
|
+
security warning. This fallback is only intended for integration testing in
|
|
15
|
+
environments where PyJWT cannot be installed.
|
|
16
|
+
|
|
17
|
+
SECURITY NOTE: The "none" provider is safe for development only. In
|
|
18
|
+
production, always configure a real SSO provider with JWKS validation.
|
|
19
|
+
|
|
20
|
+
Design decisions:
|
|
21
|
+
- Returns None for any invalid/expired/malformed token so callers always get
|
|
22
|
+
a typed result and never see an exception.
|
|
23
|
+
- extract_identity() is pure (no network I/O) and is always safe to call
|
|
24
|
+
on the dict returned by validate_token().
|
|
25
|
+
"""
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import base64
|
|
29
|
+
import json
|
|
30
|
+
import logging
|
|
31
|
+
from typing import Optional
|
|
32
|
+
from urllib.parse import urlparse
|
|
33
|
+
|
|
34
|
+
logger = logging.getLogger("devtorch.gateway.sso")
|
|
35
|
+
|
|
36
|
+
# Supported providers (besides "none")
|
|
37
|
+
_JWT_PROVIDERS = {"okta", "azure-ad", "google"}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class SSOValidator:
|
|
41
|
+
"""
|
|
42
|
+
Validate SSO/JWT tokens and extract developer identity.
|
|
43
|
+
|
|
44
|
+
Parameters
|
|
45
|
+
----------
|
|
46
|
+
provider:
|
|
47
|
+
One of "none", "okta", "azure-ad", "google".
|
|
48
|
+
jwks_url:
|
|
49
|
+
URL to the JWKS endpoint for the provider.
|
|
50
|
+
Required when provider != "none" and signature validation is needed.
|
|
51
|
+
audience:
|
|
52
|
+
Expected JWT audience claim ("aud").
|
|
53
|
+
token_issuer:
|
|
54
|
+
Optional expected JWT issuer ("iss"). Enforced only when provided.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def __init__(
|
|
58
|
+
self,
|
|
59
|
+
provider: str = "none",
|
|
60
|
+
jwks_url: str = "",
|
|
61
|
+
audience: str = "",
|
|
62
|
+
token_issuer: str = "",
|
|
63
|
+
) -> None:
|
|
64
|
+
self._provider = provider.lower()
|
|
65
|
+
self._jwks_url = jwks_url
|
|
66
|
+
self._audience = audience
|
|
67
|
+
self._token_issuer = token_issuer
|
|
68
|
+
|
|
69
|
+
if self._provider not in {"none"} | _JWT_PROVIDERS:
|
|
70
|
+
logger.warning(
|
|
71
|
+
"SSOValidator: unknown provider '%s'; treating as 'none'",
|
|
72
|
+
provider,
|
|
73
|
+
)
|
|
74
|
+
self._provider = "none"
|
|
75
|
+
|
|
76
|
+
def __repr__(self) -> str:
|
|
77
|
+
return (
|
|
78
|
+
f"{self.__class__.__name__}("
|
|
79
|
+
f"provider={self._provider!r}, "
|
|
80
|
+
f"jwks_url={self.jwks_url!r}, "
|
|
81
|
+
f"audience={self._audience!r}, "
|
|
82
|
+
f"token_issuer={self._token_issuer!r})"
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def jwks_url(self) -> str:
|
|
87
|
+
"""Return the JWKS URL with any query-string / path secret-ish tail masked."""
|
|
88
|
+
return self._mask_url(self._jwks_url)
|
|
89
|
+
|
|
90
|
+
@staticmethod
|
|
91
|
+
def _mask_url(url: str) -> str:
|
|
92
|
+
if not url:
|
|
93
|
+
return ""
|
|
94
|
+
parsed = urlparse(url)
|
|
95
|
+
if parsed.username or parsed.password:
|
|
96
|
+
# Mask credentials in the netloc
|
|
97
|
+
host = parsed.hostname or ""
|
|
98
|
+
port = parsed.port
|
|
99
|
+
port_part = f":{port}" if port else ""
|
|
100
|
+
netloc = f"{parsed.username or ''}:***@{host}{port_part}" if parsed.password else f"***@{host}{port_part}"
|
|
101
|
+
return parsed._replace(netloc=netloc).geturl()
|
|
102
|
+
# Mask last path/query component if it looks like a secret
|
|
103
|
+
if "?" in url:
|
|
104
|
+
base, _ = url.split("?", 1)
|
|
105
|
+
return f"{base}?***"
|
|
106
|
+
return url
|
|
107
|
+
|
|
108
|
+
# ------------------------------------------------------------------
|
|
109
|
+
# Public API
|
|
110
|
+
# ------------------------------------------------------------------
|
|
111
|
+
|
|
112
|
+
def validate_token(self, token: str) -> Optional[dict]:
|
|
113
|
+
"""
|
|
114
|
+
Validate *token* and return the decoded JWT claims dict.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
dict — decoded claims on success
|
|
118
|
+
None — invalid / expired / malformed token
|
|
119
|
+
"""
|
|
120
|
+
if self._provider == "none":
|
|
121
|
+
# Development / no-auth mode — always succeeds.
|
|
122
|
+
return {
|
|
123
|
+
"sub": "anonymous",
|
|
124
|
+
"email": "anonymous@local",
|
|
125
|
+
"name": "Anonymous",
|
|
126
|
+
"provider": "none",
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if not token:
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
# Real SSO providers — attempt JWT validation.
|
|
133
|
+
try:
|
|
134
|
+
return self._validate_jwt(token)
|
|
135
|
+
except Exception as exc:
|
|
136
|
+
logger.debug("SSOValidator: token validation error — %s", exc)
|
|
137
|
+
return None
|
|
138
|
+
|
|
139
|
+
def extract_identity(self, claims: dict) -> dict:
|
|
140
|
+
"""
|
|
141
|
+
Extract a normalised identity dict from raw JWT claims.
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
{
|
|
145
|
+
"user_id": str, # JWT 'sub' claim (or 'upn' for Azure AD)
|
|
146
|
+
"email": str, # JWT 'email' claim (or 'preferred_username' for Azure AD)
|
|
147
|
+
"display_name": str, # JWT 'name' claim
|
|
148
|
+
}
|
|
149
|
+
"""
|
|
150
|
+
user_id = str(claims.get("sub", ""))
|
|
151
|
+
email = str(claims.get("email", ""))
|
|
152
|
+
display_name = str(claims.get("name", ""))
|
|
153
|
+
|
|
154
|
+
if self._provider == "azure-ad":
|
|
155
|
+
if not user_id:
|
|
156
|
+
user_id = str(claims.get("upn", ""))
|
|
157
|
+
if not email:
|
|
158
|
+
email = str(claims.get("preferred_username", ""))
|
|
159
|
+
|
|
160
|
+
if not user_id:
|
|
161
|
+
user_id = "unknown"
|
|
162
|
+
if not email:
|
|
163
|
+
email = ""
|
|
164
|
+
if not display_name:
|
|
165
|
+
display_name = email or user_id or "unknown"
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
"user_id": user_id,
|
|
169
|
+
"email": email,
|
|
170
|
+
"display_name": display_name,
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
# ------------------------------------------------------------------
|
|
174
|
+
# Internal helpers
|
|
175
|
+
# ------------------------------------------------------------------
|
|
176
|
+
|
|
177
|
+
def _validate_jwt(self, token: str) -> Optional[dict]:
|
|
178
|
+
"""
|
|
179
|
+
Attempt JWT validation using PyJWT.
|
|
180
|
+
|
|
181
|
+
Real SSO providers *always* require a configured ``jwks_url`` and a
|
|
182
|
+
valid signature. If PyJWT is missing or the JWKS URL is absent, the
|
|
183
|
+
token is rejected rather than accepted via an insecure fallback.
|
|
184
|
+
"""
|
|
185
|
+
try:
|
|
186
|
+
import jwt # type: ignore[import]
|
|
187
|
+
except ImportError:
|
|
188
|
+
logger.warning(
|
|
189
|
+
"SSOValidator: PyJWT not installed; cannot validate JWT for "
|
|
190
|
+
"provider '%s'. Install PyJWT: pip install PyJWT",
|
|
191
|
+
self._provider,
|
|
192
|
+
)
|
|
193
|
+
return None
|
|
194
|
+
|
|
195
|
+
return self._validate_with_pyjwt(jwt, token)
|
|
196
|
+
|
|
197
|
+
def _validate_with_pyjwt(self, jwt_module: object, token: str) -> Optional[dict]:
|
|
198
|
+
"""
|
|
199
|
+
Decode and verify a JWT using PyJWT.
|
|
200
|
+
|
|
201
|
+
A ``jwks_url`` is mandatory for real providers. If it is missing, the
|
|
202
|
+
token is rejected instead of being decoded without signature
|
|
203
|
+
verification.
|
|
204
|
+
"""
|
|
205
|
+
try:
|
|
206
|
+
import jwt # type: ignore[import]
|
|
207
|
+
|
|
208
|
+
if not self._jwks_url:
|
|
209
|
+
logger.warning(
|
|
210
|
+
"SSOValidator: no jwks_url configured for provider '%s'; "
|
|
211
|
+
"rejecting JWT.",
|
|
212
|
+
self._provider,
|
|
213
|
+
)
|
|
214
|
+
return None
|
|
215
|
+
|
|
216
|
+
jwks_client = jwt.PyJWKClient(self._jwks_url)
|
|
217
|
+
signing_key = jwks_client.get_signing_key_from_jwt(token)
|
|
218
|
+
decode_kwargs: dict = {
|
|
219
|
+
"algorithms": ["RS256"],
|
|
220
|
+
"options": {"verify_exp": True},
|
|
221
|
+
}
|
|
222
|
+
if self._audience:
|
|
223
|
+
decode_kwargs["audience"] = self._audience
|
|
224
|
+
if self._token_issuer:
|
|
225
|
+
decode_kwargs["issuer"] = self._token_issuer
|
|
226
|
+
return jwt.decode(
|
|
227
|
+
token,
|
|
228
|
+
signing_key.key,
|
|
229
|
+
**decode_kwargs,
|
|
230
|
+
) # type: ignore[return-value]
|
|
231
|
+
except Exception as exc:
|
|
232
|
+
logger.debug("PyJWT decode failed: %s", exc)
|
|
233
|
+
return None
|