dominus-sdk-python 3.0.2__tar.gz → 3.0.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/PKG-INFO +2 -1
  2. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/README.md +1 -0
  3. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/dominus/config/endpoints.py +16 -16
  4. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/dominus/namespaces/authority.py +267 -62
  5. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/dominus/namespaces/db.py +4 -4
  6. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/dominus/namespaces/secrets.py +1 -1
  7. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/dominus/namespaces/workflow.py +10 -10
  8. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/dominus_sdk_python.egg-info/PKG-INFO +2 -1
  9. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/pyproject.toml +1 -1
  10. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/tests/test_authority_public_vocabulary.py +83 -12
  11. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/tests/test_provisioning_parity.py +2 -2
  12. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/dominus/__init__.py +0 -0
  13. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/dominus/config/__init__.py +0 -0
  14. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/dominus/errors.py +0 -0
  15. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/dominus/helpers/__init__.py +0 -0
  16. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/dominus/helpers/auth.py +0 -0
  17. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/dominus/helpers/cache.py +0 -0
  18. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/dominus/helpers/console_capture.py +0 -0
  19. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/dominus/helpers/core.py +0 -0
  20. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/dominus/helpers/crypto.py +0 -0
  21. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/dominus/helpers/sse.py +0 -0
  22. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/dominus/helpers/trace.py +0 -0
  23. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/dominus/namespaces/__init__.py +0 -0
  24. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/dominus/namespaces/admin.py +0 -0
  25. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/dominus/namespaces/ai.py +0 -0
  26. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/dominus/namespaces/artifacts.py +0 -0
  27. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/dominus/namespaces/auth.py +0 -0
  28. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/dominus/namespaces/courier.py +0 -0
  29. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/dominus/namespaces/ddl.py +0 -0
  30. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/dominus/namespaces/fastapi.py +0 -0
  31. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/dominus/namespaces/files.py +0 -0
  32. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/dominus/namespaces/health.py +0 -0
  33. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/dominus/namespaces/jobs.py +0 -0
  34. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/dominus/namespaces/logs.py +0 -0
  35. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/dominus/namespaces/portal.py +0 -0
  36. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/dominus/namespaces/processor.py +0 -0
  37. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/dominus/namespaces/redis.py +0 -0
  38. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/dominus/namespaces/secure.py +0 -0
  39. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/dominus/namespaces/sync.py +0 -0
  40. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/dominus/services/__init__.py +0 -0
  41. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/dominus/start.py +0 -0
  42. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/dominus_sdk_python.egg-info/SOURCES.txt +0 -0
  43. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/dominus_sdk_python.egg-info/dependency_links.txt +0 -0
  44. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/dominus_sdk_python.egg-info/requires.txt +0 -0
  45. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/dominus_sdk_python.egg-info/top_level.txt +0 -0
  46. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/setup.cfg +0 -0
  47. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/tests/test_auth.py +0 -0
  48. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/tests/test_errors.py +0 -0
  49. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/tests/test_flat_commands.py +0 -0
  50. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/tests/test_health.py +0 -0
  51. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/tests/test_logs.py +0 -0
  52. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/tests/test_public_exports.py +0 -0
  53. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/tests/test_transport_compat.py +0 -0
  54. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/tests/test_workflow_lifecycle.py +0 -0
  55. {dominus_sdk_python-3.0.2 → dominus_sdk_python-3.0.3}/tests/test_workflow_refs.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dominus-sdk-python
3
- Version: 3.0.2
3
+ Version: 3.0.3
4
4
  Summary: Python SDK for the Dominus gateway-first platform
5
5
  Author-email: CareBridge Systems <dev@carebridge.io>
6
6
  License: Proprietary
@@ -95,6 +95,7 @@ JWT and selected scope headers directly through Gateway.
95
95
 
96
96
  ## Transport Model
97
97
 
98
+ - Default HTTP targets are the production gateway (`https://gateway.getdominus.app`); they do not change based on your app’s git branch or PyPI package variant. Set `DOMINUS_GATEWAY_URL` (or `DOMINUS_BASE_URL` / `DOMINUS_JWT_URL`) only for local or custom routing.
98
99
  - `DOMINUS_TOKEN` is exchanged for a JWT through `POST /jwt/mint`
99
100
  - Service JWTs are cached for 14 minutes with a 60-second refresh window
100
101
  - Auth-required worker routes still send base64-encoded JSON bodies as `text/plain`
@@ -62,6 +62,7 @@ JWT and selected scope headers directly through Gateway.
62
62
 
63
63
  ## Transport Model
64
64
 
65
+ - Default HTTP targets are the production gateway (`https://gateway.getdominus.app`); they do not change based on your app’s git branch or PyPI package variant. Set `DOMINUS_GATEWAY_URL` (or `DOMINUS_BASE_URL` / `DOMINUS_JWT_URL`) only for local or custom routing.
65
66
  - `DOMINUS_TOKEN` is exchanged for a JWT through `POST /jwt/mint`
66
67
  - Service JWTs are cached for 14 minutes with a 60-second refresh window
67
68
  - Auth-required worker routes still send base64-encoded JSON bodies as `text/plain`
@@ -2,13 +2,17 @@
2
2
  Dominus SDK Endpoints
3
3
 
4
4
  Gateway URL for service routing and health checks.
5
- JWT URL for authentication (JWT minting).
5
+ JWT URL for authentication (JWT minting) — defaults to the same host as the gateway.
6
6
  Logs URL for log ingestion.
7
7
  Base URL for SDK requests (defaults to gateway).
8
8
 
9
+ Defaults always target the production gateway. They do not depend on the caller’s
10
+ git branch, PyPI package variant, or deployment environment. Override only with
11
+ DOMINUS_* env vars for local or advanced routing.
12
+
9
13
  Configuration:
10
14
  Set DOMINUS_GATEWAY_URL to override the gateway URL.
11
- Set DOMINUS_JWT_URL to override the JWT minting URL.
15
+ Set DOMINUS_JWT_URL to override the JWT minting URL (defaults to gateway URL).
12
16
  Set DOMINUS_LOGS_URL to override the logs URL.
13
17
  Set DOMINUS_BASE_URL to override the SDK request base URL.
14
18
  Example: export DOMINUS_BASE_URL=http://localhost:5000
@@ -18,23 +22,21 @@ Usage:
18
22
  """
19
23
  import os
20
24
 
21
- # Default URLs - Cloudflare Workers
25
+ # Production gateway (Cloudflare Worker). Canonical default for all SDK HTTP traffic.
22
26
  _DEFAULT_GATEWAY_URL = "https://gateway.getdominus.app"
23
- _DEFAULT_JWT_URL = "https://jwt.getdominus.app"
24
27
  _DEFAULT_LOGS_URL = "https://logs.getdominus.app"
25
- _DEFAULT_BASE_URL = _DEFAULT_GATEWAY_URL
26
28
 
27
29
  # Gateway URL for service routing (can be overridden via DOMINUS_GATEWAY_URL)
28
30
  GATEWAY_URL = os.environ.get("DOMINUS_GATEWAY_URL", _DEFAULT_GATEWAY_URL)
29
31
 
30
- # JWT URL for authentication (can be overridden via DOMINUS_JWT_URL)
31
- JWT_URL = os.environ.get("DOMINUS_JWT_URL", _DEFAULT_JWT_URL)
32
+ # JWT mint/JWKS: same host as gateway by default (matches Node SDK).
33
+ JWT_URL = os.environ.get("DOMINUS_JWT_URL", GATEWAY_URL)
32
34
 
33
35
  # Logs URL for log ingestion (can be overridden via DOMINUS_LOGS_URL)
34
36
  LOGS_URL = os.environ.get("DOMINUS_LOGS_URL", _DEFAULT_LOGS_URL)
35
37
 
36
- # Base URL for SDK requests (can be overridden via DOMINUS_BASE_URL environment variable)
37
- BASE_URL = os.environ.get("DOMINUS_BASE_URL", _DEFAULT_BASE_URL)
38
+ # Base URL for non-gateway SDK paths defaults to the same resolved gateway URL.
39
+ BASE_URL = os.environ.get("DOMINUS_BASE_URL", GATEWAY_URL)
38
40
 
39
41
  # Legacy aliases retained as gateway-first aliases.
40
42
  SOVEREIGN_URL = BASE_URL
@@ -77,12 +79,11 @@ def get_gateway_url() -> str:
77
79
 
78
80
  def get_jwt_url() -> str:
79
81
  """
80
- Get the JWT worker URL for authentication (JWT minting/validation).
82
+ Get the base URL for JWT mint/JWKS (defaults to the same host as the gateway).
81
83
 
82
- Returns the value of DOMINUS_JWT_URL environment variable if set,
83
- otherwise returns the default Cloudflare Worker URL.
84
+ Returns DOMINUS_JWT_URL if set, otherwise the same resolution as get_gateway_url().
84
85
  """
85
- return os.environ.get("DOMINUS_JWT_URL", _DEFAULT_JWT_URL)
86
+ return os.environ.get("DOMINUS_JWT_URL", get_gateway_url())
86
87
 
87
88
 
88
89
  def get_logs_url() -> str:
@@ -99,10 +100,9 @@ def get_base_url() -> str:
99
100
  """
100
101
  Get the SDK request base URL.
101
102
 
102
- Returns the value of DOMINUS_BASE_URL environment variable if set,
103
- otherwise returns the default gateway URL.
103
+ Returns DOMINUS_BASE_URL if set, otherwise the same resolution as get_gateway_url().
104
104
  """
105
- return os.environ.get("DOMINUS_BASE_URL", _DEFAULT_BASE_URL)
105
+ return os.environ.get("DOMINUS_BASE_URL", get_gateway_url())
106
106
 
107
107
 
108
108
  # DEPRECATED - use get_base_url()
@@ -37,6 +37,17 @@ def _compact(payload: Mapping[str, Any]) -> Dict[str, Any]:
37
37
  return out
38
38
 
39
39
 
40
+ def _normalize_run_kind_token(raw: Optional[str]) -> str:
41
+ if not raw:
42
+ return ""
43
+ s = str(raw).strip().lower().replace(".", "_").replace("-", "_")
44
+ return s
45
+
46
+
47
+ def _is_company_bootstrap_run_kind(run_kind: Optional[str]) -> bool:
48
+ return _normalize_run_kind_token(run_kind) == "company_bootstrap"
49
+
50
+
40
51
  def _query_string(params: Mapping[str, Any]) -> str:
41
52
  cleaned = {k: v for k, v in params.items() if v is not None and v != ""}
42
53
  if not cleaned:
@@ -110,17 +121,37 @@ class AuthorityNamespace:
110
121
  ) -> Dict[str, Any]:
111
122
  out: Dict[str, Any] = {}
112
123
  if app_slug:
113
- out["project_slug"] = app_slug
124
+ out["app_slug"] = app_slug
114
125
  if env:
115
- out["environment"] = env
126
+ out["env"] = env
116
127
  if target_org_id:
117
- out["target_project_id"] = target_org_id
128
+ out["target_org_id"] = target_org_id
118
129
  if target_app_slug:
119
- out["target_project_slug"] = target_app_slug
130
+ out["target_app_slug"] = target_app_slug
120
131
  if target_env:
121
- out["target_environment"] = target_env
132
+ out["target_env"] = target_env
122
133
  return out
123
134
 
135
+ def _target_query_params(
136
+ self,
137
+ target_org_id: Optional[str],
138
+ target_env: Optional[str],
139
+ ) -> tuple[Optional[str], Optional[str]]:
140
+ """
141
+ Omit target_org_id / target_env from GET query strings when they
142
+ duplicate the JWT gateway org/env (selected-scope MCP and operators).
143
+ """
144
+ client = self._client
145
+ gw_org = getattr(client, "_gateway_org_id", None)
146
+ gw_env = getattr(client, "_gateway_env", None)
147
+ tid = target_org_id
148
+ tev = target_env
149
+ if tid is not None and gw_org is not None and str(tid) == str(gw_org):
150
+ tid = None
151
+ if tev is not None and gw_env is not None and str(tev) == str(gw_env):
152
+ tev = None
153
+ return tid, tev
154
+
124
155
  @staticmethod
125
156
  def _http_timeout(explicit: Optional[float], default: float) -> float:
126
157
  """Use explicit client timeout when provided; otherwise the historical SDK default."""
@@ -135,6 +166,9 @@ class AuthorityNamespace:
135
166
  *,
136
167
  workflow_id: Optional[str] = None,
137
168
  workflow_ref: Optional[str] = None,
169
+ run_kind: Optional[str] = None,
170
+ company_slug: Optional[str] = None,
171
+ slug: Optional[str] = None,
138
172
  instance_id: Optional[str] = None,
139
173
  instance_key: Optional[str] = None,
140
174
  group: Optional[str] = None,
@@ -149,6 +183,18 @@ class AuthorityNamespace:
149
183
  env: Optional[str] = None,
150
184
  target_org_id: Optional[str] = None,
151
185
  target_env: Optional[str] = None,
186
+ target_app_slug: Optional[str] = None,
187
+ shared_app_slug: Optional[str] = None,
188
+ execution_mode: Optional[str] = None,
189
+ region: Optional[str] = None,
190
+ system: Optional[str] = None,
191
+ github_mode: Optional[str] = None,
192
+ overwrite_existing: Optional[bool] = None,
193
+ owner_email: Optional[str] = None,
194
+ include: Optional[Dict[str, Any]] = None,
195
+ metadata: Optional[Dict[str, Any]] = None,
196
+ display_name: Optional[str] = None,
197
+ description: Optional[str] = None,
152
198
  initiator_type: Optional[str] = None,
153
199
  initiator_id: Optional[str] = None,
154
200
  idempotency_key: Optional[str] = None,
@@ -158,30 +204,79 @@ class AuthorityNamespace:
158
204
  Ensure (and optionally launch) an Authority-backed workflow run.
159
205
 
160
206
  Calls ``POST /api/authority/runs/ensure``.
207
+
208
+ For company bootstrap, pass ``run_kind="company.bootstrap"`` (or ``company_bootstrap``) plus
209
+ ``company``, ``company_slug``, or ``slug``. Optional bootstrap fields mirror
210
+ ``bootstrap_company`` (``execution_mode``, ``region``, …).
161
211
  """
162
- if not workflow_id and not workflow_ref:
163
- raise ValueError("ensure_run requires workflow_id or workflow_ref")
212
+ bootstrap = _is_company_bootstrap_run_kind(run_kind)
213
+ if not bootstrap and not workflow_id and not workflow_ref:
214
+ raise ValueError(
215
+ "ensure_run requires workflow_id or workflow_ref unless run_kind is "
216
+ "company.bootstrap / company_bootstrap"
217
+ )
218
+ if bootstrap:
219
+ co = (company_slug or slug or company or "").strip()
220
+ if not co:
221
+ raise ValueError(
222
+ "ensure_run with run_kind company.bootstrap requires company, company_slug, or slug"
223
+ )
164
224
  if mode == "streaming":
165
225
  raise ValueError(
166
226
  "Authority runs do not support streaming mode; "
167
227
  "use blocking, async, or ensure_only"
168
228
  )
169
- body = _compact({
170
- "workflow_id": workflow_id,
171
- "workflow_ref": workflow_ref,
172
- "instance_id": instance_id,
173
- "instance_key": instance_key,
174
- "group": group,
175
- "owner": owner,
176
- "subject": subject,
177
- "company": company,
178
- "mode": mode,
179
- "bindings": bindings,
180
- "inputs": inputs,
181
- "context": context,
182
- **self._initiator(initiator_type, initiator_id, idempotency_key),
183
- **self._scope(app_slug, env, target_org_id, target_env),
184
- })
229
+
230
+ initiator = self._initiator(initiator_type, initiator_id, idempotency_key)
231
+ scope = self._scope(app_slug, env, target_org_id, target_env, target_app_slug=target_app_slug)
232
+
233
+ if bootstrap:
234
+ body = _compact({
235
+ "run_kind": run_kind,
236
+ "workflow_id": workflow_id,
237
+ "workflow_ref": workflow_ref,
238
+ "instance_id": instance_id,
239
+ "instance_key": instance_key,
240
+ "group": group,
241
+ "owner": owner,
242
+ "subject": subject,
243
+ "company": company,
244
+ "company_slug": company_slug,
245
+ "slug": slug,
246
+ "bindings": bindings,
247
+ "inputs": inputs,
248
+ "context": context,
249
+ "execution_mode": execution_mode,
250
+ "region": region,
251
+ "system": system,
252
+ "github_mode": github_mode,
253
+ "overwrite_existing": overwrite_existing,
254
+ "owner_email": owner_email,
255
+ "include": include,
256
+ "metadata": metadata,
257
+ "display_name": display_name,
258
+ "description": description,
259
+ "shared_app_slug": shared_app_slug,
260
+ **initiator,
261
+ **scope,
262
+ })
263
+ else:
264
+ body = _compact({
265
+ "workflow_id": workflow_id,
266
+ "workflow_ref": workflow_ref,
267
+ "instance_id": instance_id,
268
+ "instance_key": instance_key,
269
+ "group": group,
270
+ "owner": owner,
271
+ "subject": subject,
272
+ "company": company,
273
+ "mode": mode,
274
+ "bindings": bindings,
275
+ "inputs": inputs,
276
+ "context": context,
277
+ **initiator,
278
+ **scope,
279
+ })
185
280
  return await self._post(
186
281
  "/api/authority/runs/ensure",
187
282
  body,
@@ -213,15 +308,16 @@ class AuthorityNamespace:
213
308
  timeout: Optional[float] = None,
214
309
  ) -> Dict[str, Any]:
215
310
  """List Authority-backed runs. ``GET /api/authority/runs``."""
311
+ tid, tev = self._target_query_params(target_org_id, target_env)
216
312
  qs = _query_string({
217
313
  "workflow_id": workflow_id,
218
314
  "status": status,
219
315
  "group": group,
220
316
  "owner": owner,
221
- "project_slug": app_slug,
222
- "environment": env,
223
- "target_project_id": target_org_id,
224
- "target_environment": target_env,
317
+ "app_slug": app_slug,
318
+ "env": env,
319
+ "target_org_id": tid,
320
+ "target_env": tev,
225
321
  "limit": limit,
226
322
  "offset": offset,
227
323
  })
@@ -418,14 +514,15 @@ class AuthorityNamespace:
418
514
  offset: int = 0,
419
515
  ) -> Dict[str, Any]:
420
516
  """List Authority workflow bindings. ``GET /api/authority/workflow-bindings``."""
517
+ tid, tev = self._target_query_params(target_org_id, target_env)
421
518
  qs = _query_string({
422
519
  "workflow_ref": workflow_ref,
423
520
  "workflow_id": workflow_id,
424
521
  "status": status,
425
- "project_slug": app_slug,
426
- "environment": env,
427
- "target_project_id": target_org_id,
428
- "target_environment": target_env,
522
+ "app_slug": app_slug,
523
+ "env": env,
524
+ "target_org_id": tid,
525
+ "target_env": tev,
429
526
  "limit": limit,
430
527
  "offset": offset,
431
528
  })
@@ -444,13 +541,14 @@ class AuthorityNamespace:
444
541
  offset: int = 0,
445
542
  ) -> Dict[str, Any]:
446
543
  """List Authority workflow publications. ``GET /api/authority/workflow-publications``."""
544
+ tid, tev = self._target_query_params(target_org_id, target_env)
447
545
  qs = _query_string({
448
546
  "workflow_ref": workflow_ref,
449
547
  "status": status,
450
- "project_slug": app_slug,
451
- "environment": env,
452
- "target_project_id": target_org_id,
453
- "target_environment": target_env,
548
+ "app_slug": app_slug,
549
+ "env": env,
550
+ "target_org_id": tid,
551
+ "target_env": tev,
454
552
  "limit": limit,
455
553
  "offset": offset,
456
554
  })
@@ -510,11 +608,12 @@ class AuthorityNamespace:
510
608
  """Get an Authority company. ``GET /api/authority/companies/{company}``."""
511
609
  if not company_slug:
512
610
  raise ValueError("company_slug is required")
611
+ tid, tev = self._target_query_params(target_org_id, target_env)
513
612
  qs = _query_string({
514
- "project_slug": app_slug,
515
- "environment": env,
516
- "target_project_id": target_org_id,
517
- "target_environment": target_env,
613
+ "app_slug": app_slug,
614
+ "env": env,
615
+ "target_org_id": tid,
616
+ "target_env": tev,
518
617
  })
519
618
  return await self._get(
520
619
  f"/api/authority/companies/{quote(company_slug, safe='')}{qs}",
@@ -534,11 +633,12 @@ class AuthorityNamespace:
534
633
  timeout: Optional[float] = None,
535
634
  ) -> Dict[str, Any]:
536
635
  """List Authority companies. ``GET /api/authority/companies``."""
636
+ tid, tev = self._target_query_params(target_org_id, target_env)
537
637
  qs = _query_string({
538
- "project_slug": app_slug,
539
- "environment": env,
540
- "target_project_id": target_org_id,
541
- "target_environment": target_env,
638
+ "app_slug": app_slug,
639
+ "env": env,
640
+ "target_org_id": tid,
641
+ "target_env": tev,
542
642
  "status": status,
543
643
  "limit": limit,
544
644
  "offset": offset,
@@ -584,7 +684,7 @@ class AuthorityNamespace:
584
684
  "action": action,
585
685
  "run_id": run_id,
586
686
  "reason": reason,
587
- "shared_project_slug": shared_app_slug,
687
+ "shared_app_slug": shared_app_slug,
588
688
  "region": region,
589
689
  "system": system,
590
690
  "github_mode": github_mode,
@@ -625,8 +725,8 @@ class AuthorityNamespace:
625
725
  if not company_slug:
626
726
  raise ValueError("company_slug is required")
627
727
  qs = _query_string({
628
- "project_slug": app_slug,
629
- "environment": env,
728
+ "app_slug": app_slug,
729
+ "env": env,
630
730
  "run_id": run_id,
631
731
  })
632
732
  return await self._get(
@@ -647,11 +747,12 @@ class AuthorityNamespace:
647
747
  """Get full Authority dossier for a company. ``GET /api/authority/dossiers/company/{company}``."""
648
748
  if not company_slug:
649
749
  raise ValueError("company_slug is required")
750
+ tid, tev = self._target_query_params(target_org_id, target_env)
650
751
  qs = _query_string({
651
- "project_slug": app_slug,
652
- "environment": env,
653
- "target_project_id": target_org_id,
654
- "target_environment": target_env,
752
+ "app_slug": app_slug,
753
+ "env": env,
754
+ "target_org_id": tid,
755
+ "target_env": tev,
655
756
  })
656
757
  return await self._get(
657
758
  f"/api/authority/dossiers/company/{quote(company_slug, safe='')}{qs}",
@@ -726,11 +827,12 @@ class AuthorityNamespace:
726
827
  timeout: Optional[float] = None,
727
828
  ) -> Dict[str, Any]:
728
829
  """List deploys. ``GET /api/authority/deploys``."""
830
+ tid, tev = self._target_query_params(target_org_id, target_env)
729
831
  qs = _query_string({
730
- "project_slug": app_slug,
731
- "environment": env,
732
- "target_project_id": target_org_id,
733
- "target_environment": target_env,
832
+ "app_slug": app_slug,
833
+ "env": env,
834
+ "target_org_id": tid,
835
+ "target_env": tev,
734
836
  "repo_full_name": repo_full_name,
735
837
  "status": status,
736
838
  "limit": limit,
@@ -753,6 +855,8 @@ class AuthorityNamespace:
753
855
  company: Optional[str] = None,
754
856
  subject: Optional[str] = None,
755
857
  metadata: Optional[Dict[str, Any]] = None,
858
+ force: Optional[bool] = None,
859
+ allow_duplicate_sha: Optional[bool] = None,
756
860
  app_slug: Optional[str] = None,
757
861
  env: Optional[str] = None,
758
862
  target_org_id: Optional[str] = None,
@@ -774,6 +878,8 @@ class AuthorityNamespace:
774
878
  "company": company,
775
879
  "subject": subject,
776
880
  "metadata": metadata,
881
+ "force": force,
882
+ "allow_duplicate_sha": allow_duplicate_sha,
777
883
  **self._initiator(initiator_type, initiator_id, idempotency_key),
778
884
  **self._scope(app_slug, env, target_org_id, target_env),
779
885
  })
@@ -882,7 +988,7 @@ class AuthorityNamespace:
882
988
  *,
883
989
  variant_slug: str,
884
990
  version: str,
885
- manifest_path: str,
991
+ manifest_path: Optional[str] = None,
886
992
  status: str = "published",
887
993
  storage_app_slug: Optional[str] = None,
888
994
  storage_env: Optional[str] = None,
@@ -898,19 +1004,29 @@ class AuthorityNamespace:
898
1004
  idempotency_key: Optional[str] = None,
899
1005
  timeout: Optional[float] = None,
900
1006
  ) -> Dict[str, Any]:
901
- """Publish a managed-client release. ``POST /api/authority/clients/releases``."""
1007
+ """
1008
+ Create or upsert a managed-client release.
1009
+
1010
+ ``POST /api/authority/clients/releases``.
1011
+
1012
+ Use ``status="proposed"`` to start the release FSM without publishing;
1013
+ ``manifest_path`` may be omitted for proposed releases. Use
1014
+ ``approve_client_release`` → ``publish_approved_client_release`` →
1015
+ ``distribute_client_release`` for staged rollout.
1016
+ """
902
1017
  if not variant_slug:
903
1018
  raise ValueError("publish_client_release requires variant_slug")
904
1019
  if not version:
905
1020
  raise ValueError("publish_client_release requires version")
906
- if not manifest_path:
907
- raise ValueError("publish_client_release requires manifest_path")
1021
+ status_eff = str(status or "published").strip().lower()
1022
+ if not manifest_path and status_eff != "proposed":
1023
+ raise ValueError("publish_client_release requires manifest_path unless status is proposed")
908
1024
  body = _compact({
909
1025
  "variant_slug": variant_slug,
910
1026
  "version": version,
911
1027
  "status": status,
912
- "storage_project_slug": storage_app_slug,
913
- "storage_environment": storage_env,
1028
+ "storage_app_slug": storage_app_slug,
1029
+ "storage_env": storage_env,
914
1030
  "storage_category": storage_category,
915
1031
  "manifest_path": manifest_path,
916
1032
  "bootstrapper_path": bootstrapper_path,
@@ -926,6 +1042,95 @@ class AuthorityNamespace:
926
1042
  timeout=self._http_timeout(timeout, 30.0),
927
1043
  )
928
1044
 
1045
+ async def get_client_release(
1046
+ self,
1047
+ release_id: str,
1048
+ *,
1049
+ app_slug: Optional[str] = None,
1050
+ env: Optional[str] = None,
1051
+ timeout: Optional[float] = None,
1052
+ ) -> Dict[str, Any]:
1053
+ """Fetch one release by id. ``GET /api/authority/clients/releases/{release_id}``."""
1054
+ if not release_id:
1055
+ raise ValueError("get_client_release requires release_id")
1056
+ qs = _query_string({"app_slug": app_slug, "env": env})
1057
+ return await self._get(
1058
+ f"/api/authority/clients/releases/{quote(release_id, safe='')}{qs}",
1059
+ timeout=self._http_timeout(timeout, 30.0),
1060
+ )
1061
+
1062
+ async def approve_client_release(
1063
+ self,
1064
+ release_id: str,
1065
+ *,
1066
+ app_slug: Optional[str] = None,
1067
+ env: Optional[str] = None,
1068
+ initiator_type: Optional[str] = None,
1069
+ initiator_id: Optional[str] = None,
1070
+ idempotency_key: Optional[str] = None,
1071
+ timeout: Optional[float] = None,
1072
+ ) -> Dict[str, Any]:
1073
+ """Approve a proposed release. ``POST /api/authority/clients/releases/{id}/approve``."""
1074
+ if not release_id:
1075
+ raise ValueError("approve_client_release requires release_id")
1076
+ body = _compact({
1077
+ **self._initiator(initiator_type, initiator_id, idempotency_key),
1078
+ **self._scope(app_slug, env, None, None),
1079
+ })
1080
+ return await self._post(
1081
+ f"/api/authority/clients/releases/{quote(release_id, safe='')}/approve",
1082
+ body,
1083
+ timeout=self._http_timeout(timeout, 30.0),
1084
+ )
1085
+
1086
+ async def publish_approved_client_release(
1087
+ self,
1088
+ release_id: str,
1089
+ *,
1090
+ app_slug: Optional[str] = None,
1091
+ env: Optional[str] = None,
1092
+ initiator_type: Optional[str] = None,
1093
+ initiator_id: Optional[str] = None,
1094
+ idempotency_key: Optional[str] = None,
1095
+ timeout: Optional[float] = None,
1096
+ ) -> Dict[str, Any]:
1097
+ """Publish an approved release (gateway health handshake). ``POST .../publish``."""
1098
+ if not release_id:
1099
+ raise ValueError("publish_approved_client_release requires release_id")
1100
+ body = _compact({
1101
+ **self._initiator(initiator_type, initiator_id, idempotency_key),
1102
+ **self._scope(app_slug, env, None, None),
1103
+ })
1104
+ return await self._post(
1105
+ f"/api/authority/clients/releases/{quote(release_id, safe='')}/publish",
1106
+ body,
1107
+ timeout=self._http_timeout(timeout, 30.0),
1108
+ )
1109
+
1110
+ async def distribute_client_release(
1111
+ self,
1112
+ release_id: str,
1113
+ *,
1114
+ app_slug: Optional[str] = None,
1115
+ env: Optional[str] = None,
1116
+ initiator_type: Optional[str] = None,
1117
+ initiator_id: Optional[str] = None,
1118
+ idempotency_key: Optional[str] = None,
1119
+ timeout: Optional[float] = None,
1120
+ ) -> Dict[str, Any]:
1121
+ """Mark a published release as distributed. ``POST .../distribute``."""
1122
+ if not release_id:
1123
+ raise ValueError("distribute_client_release requires release_id")
1124
+ body = _compact({
1125
+ **self._initiator(initiator_type, initiator_id, idempotency_key),
1126
+ **self._scope(app_slug, env, None, None),
1127
+ })
1128
+ return await self._post(
1129
+ f"/api/authority/clients/releases/{quote(release_id, safe='')}/distribute",
1130
+ body,
1131
+ timeout=self._http_timeout(timeout, 30.0),
1132
+ )
1133
+
929
1134
  async def create_client_bootstrap_session(
930
1135
  self,
931
1136
  *,
@@ -987,8 +1192,8 @@ class AuthorityNamespace:
987
1192
  ) -> Dict[str, Any]:
988
1193
  """List managed client installations. ``GET /api/authority/clients/installations``."""
989
1194
  qs = _query_string({
990
- "project_slug": app_slug,
991
- "environment": env,
1195
+ "app_slug": app_slug,
1196
+ "env": env,
992
1197
  "status": status,
993
1198
  "variant_slug": variant_slug,
994
1199
  "company": company,
@@ -449,7 +449,7 @@ class DbNamespace:
449
449
  async def provision_neon_complete(
450
450
  self,
451
451
  *,
452
- project_slug: str,
452
+ app_slug: str,
453
453
  region: Optional[str] = None,
454
454
  timeout: float = 300.0,
455
455
  ) -> Dict[str, Any]:
@@ -459,7 +459,7 @@ class DbNamespace:
459
459
  endpoint="/api/provision/complete",
460
460
  method="POST",
461
461
  body={
462
- "project_slug": project_slug,
462
+ "app_slug": app_slug,
463
463
  "region": region or "aws-us-east-1",
464
464
  },
465
465
  use_gateway=True,
@@ -469,7 +469,7 @@ class DbNamespace:
469
469
  async def provision_neon_delete(
470
470
  self,
471
471
  *,
472
- project_slug: str,
472
+ app_slug: str,
473
473
  timeout: float = 120.0,
474
474
  ) -> Dict[str, Any]:
475
475
  """Neon teardown via ``DELETE /api/provision/delete``."""
@@ -477,7 +477,7 @@ class DbNamespace:
477
477
  return await self._client._request(
478
478
  endpoint="/api/provision/delete",
479
479
  method="DELETE",
480
- body={"project_slug": project_slug},
480
+ body={"app_slug": app_slug},
481
481
  use_gateway=True,
482
482
  timeout=cap,
483
483
  )
@@ -199,7 +199,7 @@ class SecretsNamespace:
199
199
  endpoint="/api/warden/secrets",
200
200
  body={
201
201
  "action": "list-all-environments",
202
- "project_id": infisical_project_id,
202
+ "infisical_project_id": infisical_project_id,
203
203
  },
204
204
  use_gateway=True,
205
205
  )
@@ -246,9 +246,9 @@ class WorkflowNamespace:
246
246
  if instance_id:
247
247
  body["instance_id"] = instance_id
248
248
  if target_project_id:
249
- body["target_project_id"] = target_project_id
249
+ body["target_org_id"] = target_project_id
250
250
  if target_environment:
251
- body["target_environment"] = target_environment
251
+ body["target_env"] = target_environment
252
252
  return self._authority_mutation_body(
253
253
  body,
254
254
  idempotency_key=idempotency_key,
@@ -1035,9 +1035,9 @@ class WorkflowNamespace:
1035
1035
  if status:
1036
1036
  params.append(f"status={status}")
1037
1037
  if target_project_id:
1038
- params.append(f"target_project_id={target_project_id}")
1038
+ params.append(f"target_org_id={target_project_id}")
1039
1039
  if target_environment:
1040
- params.append(f"target_environment={target_environment}")
1040
+ params.append(f"target_env={target_environment}")
1041
1041
 
1042
1042
  result = await self._api(
1043
1043
  endpoint=f"/api/authority/runs?{'&'.join(params)}",
@@ -1114,9 +1114,9 @@ class WorkflowNamespace:
1114
1114
  if trigger_artifact:
1115
1115
  body["trigger_artifact"] = trigger_artifact
1116
1116
  if target_project_id:
1117
- body["target_project_id"] = target_project_id
1117
+ body["target_org_id"] = target_project_id
1118
1118
  if target_environment:
1119
- body["target_environment"] = target_environment
1119
+ body["target_env"] = target_environment
1120
1120
  return await self._api(
1121
1121
  endpoint=f"/api/authority/runs/{workflow_id}/nudge",
1122
1122
  body=self._authority_mutation_body(
@@ -1202,9 +1202,9 @@ class WorkflowNamespace:
1202
1202
  if trigger_artifact:
1203
1203
  body["trigger_artifact"] = trigger_artifact
1204
1204
  if target_project_id:
1205
- body["target_project_id"] = target_project_id
1205
+ body["target_org_id"] = target_project_id
1206
1206
  if target_environment:
1207
- body["target_environment"] = target_environment
1207
+ body["target_env"] = target_environment
1208
1208
  return await self._api(
1209
1209
  endpoint=f"/api/authority/runs/{workflow_id}/retry",
1210
1210
  body=self._authority_mutation_body(
@@ -1385,9 +1385,9 @@ class WorkflowNamespace:
1385
1385
  if self._is_authority_run_id(execution_id):
1386
1386
  body: Dict[str, Any] = {}
1387
1387
  if target_project_id:
1388
- body["target_project_id"] = target_project_id
1388
+ body["target_org_id"] = target_project_id
1389
1389
  if target_environment:
1390
- body["target_environment"] = target_environment
1390
+ body["target_env"] = target_environment
1391
1391
  return await self._api(
1392
1392
  endpoint=f"/api/authority/runs/{execution_id}/cancel",
1393
1393
  body=self._authority_mutation_body(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dominus-sdk-python
3
- Version: 3.0.2
3
+ Version: 3.0.3
4
4
  Summary: Python SDK for the Dominus gateway-first platform
5
5
  Author-email: CareBridge Systems <dev@carebridge.io>
6
6
  License: Proprietary
@@ -95,6 +95,7 @@ JWT and selected scope headers directly through Gateway.
95
95
 
96
96
  ## Transport Model
97
97
 
98
+ - Default HTTP targets are the production gateway (`https://gateway.getdominus.app`); they do not change based on your app’s git branch or PyPI package variant. Set `DOMINUS_GATEWAY_URL` (or `DOMINUS_BASE_URL` / `DOMINUS_JWT_URL`) only for local or custom routing.
98
99
  - `DOMINUS_TOKEN` is exchanged for a JWT through `POST /jwt/mint`
99
100
  - Service JWTs are cached for 14 minutes with a 60-second refresh window
100
101
  - Auth-required worker routes still send base64-encoded JSON bodies as `text/plain`
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "dominus-sdk-python"
7
- version = "3.0.2"
7
+ version = "3.0.3"
8
8
  description = "Python SDK for the Dominus gateway-first platform"
9
9
  readme = "README.md"
10
10
  license = {text = "Proprietary"}
@@ -78,7 +78,7 @@ def test_authority_scope_signatures_use_new_public_vocabulary():
78
78
  context_sig = inspect.signature(AuthorityNamespace.context_resolve)
79
79
  mint_sig = inspect.signature(core_helpers.mint_selected_scope_jwt)
80
80
 
81
- for name in ("app_slug", "env", "target_org_id", "target_env"):
81
+ for name in ("app_slug", "env", "target_org_id", "target_env", "run_kind", "target_app_slug"):
82
82
  assert name in ensure_sig.parameters
83
83
  for legacy in ("project_slug", "environment", "target_project_id", "target_environment"):
84
84
  assert legacy not in ensure_sig.parameters
@@ -142,21 +142,21 @@ async def test_authority_scope_methods_use_canonical_context_wire_names():
142
142
  )
143
143
 
144
144
  ensure_body = client.calls[0]["body"]
145
- assert ensure_body["project_slug"] == "carebridge"
146
- assert ensure_body["environment"] == "production"
147
- assert ensure_body["target_project_id"] == "org-123"
148
- assert ensure_body["target_environment"] == "production"
145
+ assert ensure_body["app_slug"] == "carebridge"
146
+ assert ensure_body["env"] == "production"
147
+ assert ensure_body["target_org_id"] == "org-123"
148
+ assert ensure_body["target_env"] == "production"
149
149
 
150
150
  bootstrap_body = client.calls[1]["body"]
151
- assert bootstrap_body["shared_project_slug"] == "shared-core"
152
- assert bootstrap_body["project_slug"] == "carebridge"
153
- assert bootstrap_body["target_project_slug"] == "carebridge"
154
- assert bootstrap_body["target_environment"] == "production"
151
+ assert bootstrap_body["shared_app_slug"] == "shared-core"
152
+ assert bootstrap_body["app_slug"] == "carebridge"
153
+ assert bootstrap_body["target_app_slug"] == "carebridge"
154
+ assert bootstrap_body["target_env"] == "production"
155
155
 
156
156
  release_body = client.calls[2]["body"]
157
- assert release_body["storage_project_slug"] == "storage-core"
158
- assert release_body["storage_environment"] == "production"
159
- assert release_body["project_slug"] == "carebridge"
157
+ assert release_body["storage_app_slug"] == "storage-core"
158
+ assert release_body["storage_env"] == "production"
159
+ assert release_body["app_slug"] == "carebridge"
160
160
 
161
161
  context_body = client.calls[3]["body"]
162
162
  assert context_body["org_id"] == "org-123"
@@ -190,3 +190,74 @@ async def test_selected_scope_mint_helper_uses_new_public_names_and_route_canoni
190
190
  "target_org_id": "org-123",
191
191
  "target_env": "production",
192
192
  }
193
+
194
+
195
+ @pytest.mark.asyncio
196
+ async def test_list_runs_drops_target_query_when_matching_gateway_scope():
197
+ client = FakeClient()
198
+ client._gateway_org_id = "a73627b4-071f-4d27-b964-d220464deb6e"
199
+ client._gateway_env = "production"
200
+ ns = AuthorityNamespace(client)
201
+ await ns.list_runs(
202
+ app_slug="dominus-authority",
203
+ env="production",
204
+ target_org_id="a73627b4-071f-4d27-b964-d220464deb6e",
205
+ target_env="production",
206
+ limit=5,
207
+ offset=0,
208
+ )
209
+ ep = client.calls[0]["endpoint"]
210
+ assert "target_org_id" not in ep
211
+ assert "target_env" not in ep
212
+ assert "app_slug=dominus-authority" in ep
213
+
214
+
215
+ @pytest.mark.asyncio
216
+ async def test_list_runs_keeps_target_query_when_org_differs_from_gateway():
217
+ client = FakeClient()
218
+ client._gateway_org_id = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
219
+ client._gateway_env = "production"
220
+ ns = AuthorityNamespace(client)
221
+ await ns.list_runs(
222
+ app_slug="dominus-authority",
223
+ env="production",
224
+ target_org_id="bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
225
+ target_env="production",
226
+ limit=3,
227
+ )
228
+ ep = client.calls[0]["endpoint"]
229
+ assert "target_org_id=bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb" in ep
230
+
231
+
232
+ @pytest.mark.asyncio
233
+ async def test_authority_ensure_run_company_bootstrap_omits_workflow_mode():
234
+ client = FakeClient()
235
+ namespace = AuthorityNamespace(client)
236
+ await namespace.ensure_run(
237
+ run_kind="company.bootstrap",
238
+ company="acme-corp",
239
+ app_slug="carebridge",
240
+ env="production",
241
+ target_org_id="org-1",
242
+ execution_mode="async",
243
+ shared_app_slug="shared-core",
244
+ )
245
+ body = client.calls[0]["body"]
246
+ assert body["run_kind"] == "company.bootstrap"
247
+ assert body["company"] == "acme-corp"
248
+ assert body["execution_mode"] == "async"
249
+ assert body["shared_app_slug"] == "shared-core"
250
+ assert "mode" not in body
251
+
252
+
253
+ @pytest.mark.asyncio
254
+ async def test_authority_ensure_run_bootstrap_requires_company_slug():
255
+ client = FakeClient()
256
+ namespace = AuthorityNamespace(client)
257
+ with pytest.raises(ValueError, match="company, company_slug, or slug"):
258
+ await namespace.ensure_run(
259
+ run_kind="company_bootstrap",
260
+ app_slug="carebridge",
261
+ env="production",
262
+ )
263
+ assert client.calls == []
@@ -65,8 +65,8 @@ async def test_provision_neon_delete_uses_delete_with_body():
65
65
  return {"deleted": True}
66
66
 
67
67
  with patch.object(dominus, "_request", side_effect=fake_request):
68
- await dominus.db.provision_neon_delete(project_slug="proj-x", timeout=90.0)
68
+ await dominus.db.provision_neon_delete(app_slug="proj-x", timeout=90.0)
69
69
  assert len(calls) == 1
70
70
  assert calls[0]["method"] == "DELETE"
71
- assert calls[0]["body"] == {"project_slug": "proj-x"}
71
+ assert calls[0]["body"] == {"app_slug": "proj-x"}
72
72
  assert calls[0]["endpoint"] == "/api/provision/delete"