dominus-sdk-python 5.1.0__tar.gz → 6.0.1__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 (66) hide show
  1. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/PKG-INFO +4 -11
  2. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/README.md +2 -8
  3. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/__init__.py +5 -1
  4. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/__init__.py +4 -0
  5. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/authority.py +33 -12
  6. dominus_sdk_python-6.0.1/dominus/namespaces/coder.py +221 -0
  7. dominus_sdk_python-6.0.1/dominus/namespaces/platform.py +130 -0
  8. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/recipes.py +24 -8
  9. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/workflow.py +26 -35
  10. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/start.py +4 -0
  11. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus_sdk_python.egg-info/PKG-INFO +4 -11
  12. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus_sdk_python.egg-info/SOURCES.txt +3 -0
  13. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/pyproject.toml +3 -4
  14. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/tests/test_authority_public_vocabulary.py +43 -3
  15. dominus_sdk_python-6.0.1/tests/test_platform_coder_namespaces.py +118 -0
  16. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/tests/test_public_exports.py +6 -0
  17. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/tests/test_recipes_namespace.py +36 -0
  18. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/tests/test_workflow_lifecycle.py +6 -6
  19. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/tests/test_workflow_refs.py +13 -16
  20. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/config/__init__.py +0 -0
  21. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/config/endpoints.py +0 -0
  22. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/errors.py +0 -0
  23. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/helpers/__init__.py +0 -0
  24. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/helpers/auth.py +0 -0
  25. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/helpers/cache.py +0 -0
  26. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/helpers/console_capture.py +0 -0
  27. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/helpers/core.py +0 -0
  28. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/helpers/crypto.py +0 -0
  29. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/helpers/sse.py +0 -0
  30. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/helpers/trace.py +0 -0
  31. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/admin.py +0 -0
  32. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/ai.py +0 -0
  33. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/artifacts.py +0 -0
  34. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/auth.py +0 -0
  35. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/browser.py +0 -0
  36. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/courier.py +0 -0
  37. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/db.py +0 -0
  38. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/ddl.py +0 -0
  39. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/deployer.py +0 -0
  40. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/fastapi.py +0 -0
  41. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/files.py +0 -0
  42. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/health.py +0 -0
  43. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/jobs.py +0 -0
  44. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/logs.py +0 -0
  45. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/portal.py +0 -0
  46. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/processor.py +0 -0
  47. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/redis.py +0 -0
  48. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/secrets.py +0 -0
  49. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/secure.py +0 -0
  50. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/stash.py +0 -0
  51. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/sync.py +0 -0
  52. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/namespaces/warden.py +0 -0
  53. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus/services/__init__.py +0 -0
  54. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus_sdk_python.egg-info/dependency_links.txt +0 -0
  55. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus_sdk_python.egg-info/requires.txt +0 -0
  56. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/dominus_sdk_python.egg-info/top_level.txt +0 -0
  57. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/setup.cfg +0 -0
  58. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/tests/test_auth.py +0 -0
  59. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/tests/test_browser_namespace.py +0 -0
  60. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/tests/test_control_plane_namespaces.py +0 -0
  61. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/tests/test_errors.py +0 -0
  62. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/tests/test_flat_commands.py +0 -0
  63. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/tests/test_health.py +0 -0
  64. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/tests/test_logs.py +0 -0
  65. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/tests/test_provisioning_parity.py +0 -0
  66. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.1}/tests/test_transport_compat.py +0 -0
@@ -1,15 +1,14 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dominus-sdk-python
3
- Version: 5.1.0
3
+ Version: 6.0.1
4
4
  Summary: Python SDK for the Dominus gateway-first platform
5
5
  Author-email: CareBridge Systems <dev@carebridge.io>
6
- License: Proprietary
6
+ License-Expression: LicenseRef-Proprietary
7
7
  Project-URL: Homepage, https://github.com/carebridgesystems/dominus-sdk-python
8
8
  Project-URL: Repository, https://github.com/carebridgesystems/dominus-sdk-python
9
9
  Keywords: dominus,carebridge,sdk,gateway,api,async
10
10
  Classifier: Development Status :: 4 - Beta
11
11
  Classifier: Intended Audience :: Developers
12
- Classifier: License :: Other/Proprietary License
13
12
  Classifier: Operating System :: OS Independent
14
13
  Classifier: Programming Language :: Python :: 3
15
14
  Classifier: Programming Language :: Python :: 3.9
@@ -43,7 +42,7 @@ Async Python SDK for the Dominus gateway-first service plane.
43
42
  - Gateway-scoped client mode for MCP and other user-JWT sessions
44
43
  - Transport compatibility for wrapped `{success,data}` responses and unwrapped Warden/control-plane success objects
45
44
  - Local helpers for JWT verification, trace propagation, retries, and console capture
46
- - Current package version: `5.1.0`
45
+ - Current package version: `6.0.0`
47
46
 
48
47
  ## Install
49
48
 
@@ -66,12 +65,6 @@ users = await dominus.db.query("users", filters={"status": "active"})
66
65
  await dominus.redis.set("session:123", {"user": "john"}, ttl=3600)
67
66
 
68
67
  run = await dominus.workflow.ensure(
69
- "wf://carebridge/report-cycle",
70
- subject="PCM47474562",
71
- company="summit-radiology",
72
- )
73
-
74
- recipe_run = await dominus.workflow.ensure(
75
68
  workflow_recipe_ref="recipe://workflow-recipe-v1/report-cycle@v3",
76
69
  subject="PCM47474562",
77
70
  company="summit-radiology",
@@ -199,7 +192,7 @@ JWT and selected scope headers directly through Gateway.
199
192
  | `health` | Gateway | Health and ping helpers |
200
193
  | `admin` | Admin Worker | Admin category reseed/reset |
201
194
  | `ai` | Agent Runtime | Agent, completion, RAG, artifacts, results, raw orchestration |
202
- | `workflow` | Workflow Manager + Authority | Saved workflow CRUD and canonical run lifecycle |
195
+ | `workflow` | Workflow Manager + Authority | Saved workflow CRUD and recipe-backed run lifecycle |
203
196
  | `artifacts` | Artifact Worker | Addressed V2 artifacts, bookmarks, watches |
204
197
  | `jobs` | Job Worker | Enqueue, poll, dead-letter management |
205
198
  | `processor` | Processor | Batch and single-job processing |
@@ -9,7 +9,7 @@ Async Python SDK for the Dominus gateway-first service plane.
9
9
  - Gateway-scoped client mode for MCP and other user-JWT sessions
10
10
  - Transport compatibility for wrapped `{success,data}` responses and unwrapped Warden/control-plane success objects
11
11
  - Local helpers for JWT verification, trace propagation, retries, and console capture
12
- - Current package version: `5.1.0`
12
+ - Current package version: `6.0.0`
13
13
 
14
14
  ## Install
15
15
 
@@ -32,12 +32,6 @@ users = await dominus.db.query("users", filters={"status": "active"})
32
32
  await dominus.redis.set("session:123", {"user": "john"}, ttl=3600)
33
33
 
34
34
  run = await dominus.workflow.ensure(
35
- "wf://carebridge/report-cycle",
36
- subject="PCM47474562",
37
- company="summit-radiology",
38
- )
39
-
40
- recipe_run = await dominus.workflow.ensure(
41
35
  workflow_recipe_ref="recipe://workflow-recipe-v1/report-cycle@v3",
42
36
  subject="PCM47474562",
43
37
  company="summit-radiology",
@@ -165,7 +159,7 @@ JWT and selected scope headers directly through Gateway.
165
159
  | `health` | Gateway | Health and ping helpers |
166
160
  | `admin` | Admin Worker | Admin category reseed/reset |
167
161
  | `ai` | Agent Runtime | Agent, completion, RAG, artifacts, results, raw orchestration |
168
- | `workflow` | Workflow Manager + Authority | Saved workflow CRUD and canonical run lifecycle |
162
+ | `workflow` | Workflow Manager + Authority | Saved workflow CRUD and recipe-backed run lifecycle |
169
163
  | `artifacts` | Artifact Worker | Addressed V2 artifacts, bookmarks, watches |
170
164
  | `jobs` | Job Worker | Enqueue, poll, dead-letter management |
171
165
  | `processor` | Processor | Batch and single-job processing |
@@ -112,6 +112,8 @@ from .namespaces.browser import BrowserNamespace
112
112
  from .namespaces.recipes import RecipesNamespace
113
113
  from .namespaces.deployer import DeployerNamespace
114
114
  from .namespaces.warden import WardenNamespace
115
+ from .namespaces.platform import PlatformNamespace
116
+ from .namespaces.coder import CoderNamespace
115
117
 
116
118
  # Export AI namespace for agent-runtime operations
117
119
  from .namespaces.ai import (
@@ -162,7 +164,7 @@ from .errors import (
162
164
  TimeoutError as DominusTimeoutError,
163
165
  )
164
166
 
165
- __version__ = "5.1.0"
167
+ __version__ = "6.0.1"
166
168
  __all__ = [
167
169
  # Main SDK instance
168
170
  "dominus",
@@ -213,6 +215,8 @@ __all__ = [
213
215
  "RecipesNamespace",
214
216
  "DeployerNamespace",
215
217
  "WardenNamespace",
218
+ "PlatformNamespace",
219
+ "CoderNamespace",
216
220
  # AI namespace for agent-runtime operations
217
221
  "AiNamespace",
218
222
  "RagSubNamespace",
@@ -19,6 +19,8 @@ from .browser import BrowserNamespace
19
19
  from .recipes import RecipesNamespace
20
20
  from .deployer import DeployerNamespace
21
21
  from .warden import WardenNamespace
22
+ from .platform import PlatformNamespace
23
+ from .coder import CoderNamespace
22
24
  from .ai import (
23
25
  AiNamespace,
24
26
  RagSubNamespace,
@@ -48,6 +50,8 @@ __all__ = [
48
50
  "RecipesNamespace",
49
51
  "DeployerNamespace",
50
52
  "WardenNamespace",
53
+ "PlatformNamespace",
54
+ "CoderNamespace",
51
55
  "AiNamespace",
52
56
  "RagSubNamespace",
53
57
  "ArtifactsSubNamespace",
@@ -12,9 +12,9 @@ All methods route through the gateway (``use_gateway=True``) which maps
12
12
 
13
13
  Run launch is also exposed as ``dominus.workflow.ensure(...)``. Both call
14
14
  ``/api/authority/runs/ensure``. Use ``dominus.workflow.ensure`` for the
15
- canonical "launch a saved workflow" path; use ``dominus.authority.ensure_run``
16
- when you want to think in Authority terms (run lifecycle, run dossiers,
17
- retries, cancels).
15
+ canonical recipe-backed launch path; use ``dominus.authority.ensure_run`` when
16
+ you want to think in Authority terms (run lifecycle, run dossiers, retries,
17
+ cancels).
18
18
  """
19
19
  from __future__ import annotations
20
20
 
@@ -48,6 +48,14 @@ def _is_provisioning_bootstrap_run_kind(run_kind: Optional[str]) -> bool:
48
48
  return _normalize_run_kind_token(run_kind) == "provisioning_bootstrap"
49
49
 
50
50
 
51
+ def _is_workflow_recipe_ref(value: Optional[str]) -> bool:
52
+ return str(value or "").strip().startswith("recipe://workflow-recipe-v1/")
53
+
54
+
55
+ def _is_pipeline_recipe_ref(value: Optional[str]) -> bool:
56
+ return str(value or "").strip().startswith("recipe://pipeline-recipe-v1/")
57
+
58
+
51
59
  def _query_string(params: Mapping[str, Any]) -> str:
52
60
  cleaned = {k: v for k, v in params.items() if v is not None and v != ""}
53
61
  if not cleaned:
@@ -61,7 +69,10 @@ class AuthorityNamespace:
61
69
 
62
70
  Usage::
63
71
 
64
- await dominus.authority.ensure_run(workflow_id="...", subject="PCM47474562")
72
+ await dominus.authority.ensure_run(
73
+ workflow_recipe_ref="recipe://workflow-recipe-v1/report-cycle@head",
74
+ subject="PCM47474562",
75
+ )
65
76
  await dominus.authority.list_provisioning_targets()
66
77
  await dominus.authority.get_run_dossier(run_id)
67
78
  """
@@ -167,8 +178,8 @@ class AuthorityNamespace:
167
178
  async def ensure_run(
168
179
  self,
169
180
  *,
170
- workflow_id: Optional[str] = None,
171
- workflow_ref: Optional[str] = None,
181
+ workflow_recipe_ref: Optional[str] = None,
182
+ pipeline_recipe_ref: Optional[str] = None,
172
183
  run_kind: Optional[str] = None,
173
184
  bootstrap_profile_ref: Optional[str] = None,
174
185
  provisioning_target_slug: Optional[str] = None,
@@ -213,11 +224,23 @@ class AuthorityNamespace:
213
224
  ``bootstrap_provisioning_target`` (``execution_mode``, ``region``, ``bootstrap_profile_ref``, ...).
214
225
  """
215
226
  bootstrap = _is_provisioning_bootstrap_run_kind(run_kind)
216
- if not bootstrap and not workflow_id and not workflow_ref:
227
+ recipe_sources = [
228
+ value
229
+ for value in (
230
+ str(workflow_recipe_ref or "").strip(),
231
+ str(pipeline_recipe_ref or "").strip(),
232
+ )
233
+ if value
234
+ ]
235
+ if not bootstrap and len(recipe_sources) != 1:
217
236
  raise ValueError(
218
- "ensure_run requires workflow_id or workflow_ref unless run_kind is "
237
+ "ensure_run requires exactly one of workflow_recipe_ref or pipeline_recipe_ref unless run_kind is "
219
238
  "provisioning.bootstrap"
220
239
  )
240
+ if workflow_recipe_ref and not _is_workflow_recipe_ref(workflow_recipe_ref):
241
+ raise ValueError("workflow_recipe_ref must start with recipe://workflow-recipe-v1/")
242
+ if pipeline_recipe_ref and not _is_pipeline_recipe_ref(pipeline_recipe_ref):
243
+ raise ValueError("pipeline_recipe_ref must start with recipe://pipeline-recipe-v1/")
221
244
  if bootstrap:
222
245
  target_slug = (provisioning_target_slug or "").strip()
223
246
  if not target_slug:
@@ -236,8 +259,6 @@ class AuthorityNamespace:
236
259
  "run_kind": run_kind,
237
260
  "bootstrap_profile_ref": bootstrap_profile_ref,
238
261
  "provisioning_target_slug": provisioning_target_slug,
239
- "workflow_id": workflow_id,
240
- "workflow_ref": workflow_ref,
241
262
  "instance_id": instance_id,
242
263
  "instance_key": instance_key,
243
264
  "group": group,
@@ -262,8 +283,8 @@ class AuthorityNamespace:
262
283
  })
263
284
  else:
264
285
  body = _compact({
265
- "workflow_id": workflow_id,
266
- "workflow_ref": workflow_ref,
286
+ "workflow_recipe_ref": workflow_recipe_ref,
287
+ "pipeline_recipe_ref": pipeline_recipe_ref,
267
288
  "instance_id": instance_id,
268
289
  "instance_key": instance_key,
269
290
  "group": group,
@@ -0,0 +1,221 @@
1
+ """Coder namespace.
2
+
3
+ Lifecycle helpers for dominus-coder-runtime. This surface intentionally avoids
4
+ raw shell, filesystem browsing, and credential ingress helpers; Platform
5
+ decisions and Coder Runtime own executor policy and workspace operations.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from typing import Any, Dict, Optional, TYPE_CHECKING
10
+ from urllib.parse import quote, urlencode
11
+
12
+ if TYPE_CHECKING:
13
+ from ..start import Dominus
14
+
15
+
16
+ def _compact(payload: Dict[str, Any]) -> Dict[str, Any]:
17
+ out: Dict[str, Any] = {}
18
+ for key, value in payload.items():
19
+ if value is None:
20
+ continue
21
+ if isinstance(value, str) and value.strip() == "":
22
+ continue
23
+ out[key] = value
24
+ return out
25
+
26
+
27
+ def _query_string(params: Dict[str, Any]) -> str:
28
+ cleaned = _compact(params)
29
+ return f"?{urlencode(cleaned)}" if cleaned else ""
30
+
31
+
32
+ def _normalize_coder_path(path: str) -> str:
33
+ trimmed = path.strip()
34
+ normalized = trimmed if trimmed.startswith("/") else f"/{trimmed}"
35
+ if normalized == "/svc/coder" or normalized.startswith("/svc/coder/"):
36
+ return normalized
37
+ if normalized == "/api/coder" or normalized.startswith("/api/coder/"):
38
+ return normalized.replace("/api/", "/svc/", 1)
39
+ return f"/svc/coder{normalized}"
40
+
41
+
42
+ def _mutation_body(
43
+ *,
44
+ reason: Optional[str] = None,
45
+ idempotency_key: Optional[str] = None,
46
+ metadata: Optional[Dict[str, Any]] = None,
47
+ ) -> Dict[str, Any]:
48
+ return _compact(
49
+ {
50
+ "reason": reason,
51
+ "idempotency_key": idempotency_key,
52
+ "metadata": metadata,
53
+ }
54
+ )
55
+
56
+
57
+ class CoderNamespace:
58
+ """Coder run lifecycle helpers."""
59
+
60
+ def __init__(self, client: "Dominus"):
61
+ self._client = client
62
+
63
+ async def request(
64
+ self,
65
+ path: str,
66
+ *,
67
+ method: str = "GET",
68
+ body: Optional[Dict[str, Any]] = None,
69
+ headers: Optional[Dict[str, str]] = None,
70
+ timeout: float = 30.0,
71
+ ) -> Any:
72
+ return await self._client.gateway_fetch(
73
+ _normalize_coder_path(path),
74
+ method=method,
75
+ body=body,
76
+ headers=headers,
77
+ timeout=timeout,
78
+ )
79
+
80
+ async def health(self) -> Dict[str, Any]:
81
+ return await self.request("/health", method="GET")
82
+
83
+ async def ready(self) -> Dict[str, Any]:
84
+ return await self.request("/ready", method="GET")
85
+
86
+ async def ensure_run(
87
+ self,
88
+ *,
89
+ policy_decision_id: str,
90
+ idempotency_key: Optional[str] = None,
91
+ mode: Optional[str] = None,
92
+ task_recipe_ref: Optional[str] = None,
93
+ workflow_recipe_ref: Optional[str] = None,
94
+ pipeline_recipe_ref: Optional[str] = None,
95
+ group: Optional[str] = None,
96
+ repository: Optional[str] = None,
97
+ branch: Optional[str] = None,
98
+ base_branch: Optional[str] = None,
99
+ working_branch: Optional[str] = None,
100
+ title: Optional[str] = None,
101
+ instructions: Optional[str] = None,
102
+ inputs: Optional[Dict[str, Any]] = None,
103
+ metadata: Optional[Dict[str, Any]] = None,
104
+ ) -> Dict[str, Any]:
105
+ if not policy_decision_id or policy_decision_id.strip() == "":
106
+ raise ValueError("ensure_run requires policy_decision_id")
107
+
108
+ body = _compact(
109
+ {
110
+ "policy_decision_id": policy_decision_id,
111
+ "idempotency_key": idempotency_key,
112
+ "mode": mode,
113
+ "task_recipe_ref": task_recipe_ref,
114
+ "workflow_recipe_ref": workflow_recipe_ref,
115
+ "pipeline_recipe_ref": pipeline_recipe_ref,
116
+ "group": group,
117
+ "repository": repository,
118
+ "branch": branch,
119
+ "base_branch": base_branch,
120
+ "working_branch": working_branch,
121
+ "title": title,
122
+ "instructions": instructions,
123
+ "inputs": inputs,
124
+ "metadata": metadata,
125
+ }
126
+ )
127
+ return await self.request(
128
+ "/runs/ensure",
129
+ method="POST",
130
+ body=body,
131
+ timeout=600.0,
132
+ )
133
+
134
+ async def list_runs(
135
+ self,
136
+ *,
137
+ status: Optional[str] = None,
138
+ group: Optional[str] = None,
139
+ repository: Optional[str] = None,
140
+ limit: Optional[int] = None,
141
+ offset: Optional[int] = None,
142
+ ) -> Dict[str, Any]:
143
+ qs = _query_string(
144
+ {
145
+ "status": status,
146
+ "group": group,
147
+ "repository": repository,
148
+ "limit": limit,
149
+ "offset": offset,
150
+ }
151
+ )
152
+ return await self.request(f"/runs{qs}", method="GET")
153
+
154
+ async def get_run(self, run_id: str) -> Dict[str, Any]:
155
+ return await self.request(f"/runs/{quote(run_id, safe='')}", method="GET")
156
+
157
+ async def cancel_run(
158
+ self,
159
+ run_id: str,
160
+ *,
161
+ reason: Optional[str] = None,
162
+ idempotency_key: Optional[str] = None,
163
+ metadata: Optional[Dict[str, Any]] = None,
164
+ ) -> Dict[str, Any]:
165
+ return await self.request(
166
+ f"/runs/{quote(run_id, safe='')}/cancel",
167
+ method="POST",
168
+ body=_mutation_body(
169
+ reason=reason,
170
+ idempotency_key=idempotency_key,
171
+ metadata=metadata,
172
+ ),
173
+ )
174
+
175
+ async def retry_run(
176
+ self,
177
+ run_id: str,
178
+ *,
179
+ reason: Optional[str] = None,
180
+ idempotency_key: Optional[str] = None,
181
+ metadata: Optional[Dict[str, Any]] = None,
182
+ ) -> Dict[str, Any]:
183
+ return await self.request(
184
+ f"/runs/{quote(run_id, safe='')}/retry",
185
+ method="POST",
186
+ body=_mutation_body(
187
+ reason=reason,
188
+ idempotency_key=idempotency_key,
189
+ metadata=metadata,
190
+ ),
191
+ )
192
+
193
+ async def nudge_run(
194
+ self,
195
+ run_id: str,
196
+ *,
197
+ reason: Optional[str] = None,
198
+ idempotency_key: Optional[str] = None,
199
+ metadata: Optional[Dict[str, Any]] = None,
200
+ ) -> Dict[str, Any]:
201
+ return await self.request(
202
+ f"/runs/{quote(run_id, safe='')}/nudge",
203
+ method="POST",
204
+ body=_mutation_body(
205
+ reason=reason,
206
+ idempotency_key=idempotency_key,
207
+ metadata=metadata,
208
+ ),
209
+ )
210
+
211
+ async def get_run_summary(self, run_id: str) -> Dict[str, Any]:
212
+ return await self.request(
213
+ f"/runs/{quote(run_id, safe='')}/summary",
214
+ method="GET",
215
+ )
216
+
217
+ async def get_run_artifacts(self, run_id: str) -> Dict[str, Any]:
218
+ return await self.request(
219
+ f"/runs/{quote(run_id, safe='')}/artifacts",
220
+ method="GET",
221
+ )
@@ -0,0 +1,130 @@
1
+ """Platform namespace.
2
+
3
+ Thin JSON-first helpers for the dominus-platform-worker Gateway surface.
4
+ Platform owns group/repository policy decisions; it does not execute coder work
5
+ or expose credentials.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from typing import Any, Dict, Optional, TYPE_CHECKING
10
+ from urllib.parse import quote
11
+
12
+ if TYPE_CHECKING:
13
+ from ..start import Dominus
14
+
15
+
16
+ def _normalize_platform_path(path: str) -> str:
17
+ trimmed = path.strip()
18
+ normalized = trimmed if trimmed.startswith("/") else f"/{trimmed}"
19
+ if normalized == "/svc/platform" or normalized.startswith("/svc/platform/"):
20
+ return normalized
21
+ if normalized == "/api/platform" or normalized.startswith("/api/platform/"):
22
+ return normalized.replace("/api/", "/svc/", 1)
23
+ return f"/svc/platform{normalized}"
24
+
25
+
26
+ class PlatformNamespace:
27
+ """Platform group/repository policy helpers."""
28
+
29
+ def __init__(self, client: "Dominus"):
30
+ self._client = client
31
+
32
+ async def request(
33
+ self,
34
+ path: str,
35
+ *,
36
+ method: str = "GET",
37
+ body: Optional[Dict[str, Any]] = None,
38
+ headers: Optional[Dict[str, str]] = None,
39
+ timeout: float = 30.0,
40
+ ) -> Any:
41
+ return await self._client.gateway_fetch(
42
+ _normalize_platform_path(path),
43
+ method=method,
44
+ body=body,
45
+ headers=headers,
46
+ timeout=timeout,
47
+ )
48
+
49
+ async def health(self) -> Dict[str, Any]:
50
+ return await self.request("/health", method="GET")
51
+
52
+ async def ready(self) -> Dict[str, Any]:
53
+ return await self.request("/ready", method="GET")
54
+
55
+ async def list_groups(self) -> Dict[str, Any]:
56
+ return await self.request("/groups", method="GET")
57
+
58
+ async def get_group(self, group_slug: str) -> Dict[str, Any]:
59
+ return await self.request(f"/groups/{quote(group_slug, safe='')}", method="GET")
60
+
61
+ async def upsert_group(self, group: Dict[str, Any]) -> Dict[str, Any]:
62
+ return await self.request("/groups", method="POST", body=group)
63
+
64
+ async def update_group(self, group_slug: str, group: Dict[str, Any]) -> Dict[str, Any]:
65
+ return await self.request(
66
+ f"/groups/{quote(group_slug, safe='')}",
67
+ method="PATCH",
68
+ body=group,
69
+ )
70
+
71
+ async def add_group_member(self, group_slug: str, subject_id: str) -> Dict[str, Any]:
72
+ return await self.request(
73
+ f"/groups/{quote(group_slug, safe='')}/members",
74
+ method="POST",
75
+ body={"subject_id": subject_id},
76
+ )
77
+
78
+ async def link_group_repository(self, group_slug: str, repository: str) -> Dict[str, Any]:
79
+ return await self.request(
80
+ f"/groups/{quote(group_slug, safe='')}/repositories",
81
+ method="POST",
82
+ body={"repository": repository},
83
+ )
84
+
85
+ async def list_repositories(self) -> Dict[str, Any]:
86
+ return await self.request("/repositories", method="GET")
87
+
88
+ async def get_repository(self, repository_id: str) -> Dict[str, Any]:
89
+ return await self.request(
90
+ f"/repositories/{quote(repository_id, safe='')}",
91
+ method="GET",
92
+ )
93
+
94
+ async def upsert_repository(self, repository: Dict[str, Any]) -> Dict[str, Any]:
95
+ return await self.request("/repositories", method="POST", body=repository)
96
+
97
+ async def update_repository(
98
+ self,
99
+ repository_id: str,
100
+ repository: Dict[str, Any],
101
+ ) -> Dict[str, Any]:
102
+ return await self.request(
103
+ f"/repositories/{quote(repository_id, safe='')}",
104
+ method="PATCH",
105
+ body=repository,
106
+ )
107
+
108
+ async def ensure_policy_decision(self, options: Dict[str, Any]) -> Dict[str, Any]:
109
+ return await self.request(
110
+ "/policy/decisions/ensure",
111
+ method="POST",
112
+ body=options,
113
+ )
114
+
115
+ async def get_policy_decision(self, decision_id: str) -> Dict[str, Any]:
116
+ return await self.request(
117
+ f"/policy/decisions/{quote(decision_id, safe='')}",
118
+ method="GET",
119
+ )
120
+
121
+ async def rehydrate_policy_decision(
122
+ self,
123
+ decision_id: str,
124
+ options: Optional[Dict[str, Any]] = None,
125
+ ) -> Dict[str, Any]:
126
+ return await self.request(
127
+ f"/policy/decisions/{quote(decision_id, safe='')}/rehydrate",
128
+ method="POST",
129
+ body=options or {},
130
+ )
@@ -41,14 +41,24 @@ class RecipesNamespace:
41
41
  def __init__(self, client: "Dominus"):
42
42
  self._client = client
43
43
 
44
- async def _post(self, endpoint: str, body: Optional[Dict[str, Any]] = None, *, timeout: float = 30.0) -> Dict[str, Any]:
45
- return await self._client._request(
46
- endpoint=endpoint,
47
- method="POST",
48
- body=body or {},
49
- use_gateway=True,
50
- timeout=timeout,
51
- )
44
+ async def _post(
45
+ self,
46
+ endpoint: str,
47
+ body: Optional[Dict[str, Any]] = None,
48
+ *,
49
+ timeout: float = 30.0,
50
+ actor_context: Optional[Dict[str, str]] = None,
51
+ ) -> Dict[str, Any]:
52
+ kwargs: Dict[str, Any] = {
53
+ "endpoint": endpoint,
54
+ "method": "POST",
55
+ "body": body or {},
56
+ "use_gateway": True,
57
+ "timeout": timeout,
58
+ }
59
+ if actor_context is not None:
60
+ kwargs["actor"] = actor_context
61
+ return await self._client._request(**kwargs)
52
62
 
53
63
  async def _get(self, endpoint: str, *, timeout: float = 30.0) -> Dict[str, Any]:
54
64
  return await self._client._request(
@@ -75,6 +85,7 @@ class RecipesNamespace:
75
85
  name: str,
76
86
  body: str,
77
87
  description: Optional[str] = None,
88
+ actor_context: Optional[Dict[str, str]] = None,
78
89
  timeout: float = 30.0,
79
90
  ) -> Dict[str, Any]:
80
91
  """Publish a recipe. ``POST /api/recipe/recipes/publish``."""
@@ -87,6 +98,7 @@ class RecipesNamespace:
87
98
  "body": body,
88
99
  "description": description,
89
100
  }),
101
+ actor_context=actor_context,
90
102
  timeout=timeout,
91
103
  )
92
104
 
@@ -112,6 +124,7 @@ class RecipesNamespace:
112
124
  name: str,
113
125
  version: int,
114
126
  reason: Optional[str] = None,
127
+ actor_context: Optional[Dict[str, str]] = None,
115
128
  timeout: float = 30.0,
116
129
  ) -> Dict[str, Any]:
117
130
  """Mark the current head recipe version deprecated. ``POST /api/recipe/recipes/deprecate``."""
@@ -124,6 +137,7 @@ class RecipesNamespace:
124
137
  "version": version,
125
138
  "reason": reason,
126
139
  }),
140
+ actor_context=actor_context,
127
141
  timeout=timeout,
128
142
  )
129
143
 
@@ -170,6 +184,7 @@ class RecipesNamespace:
170
184
  owning_worker: str,
171
185
  schema_version: int,
172
186
  json_schema: Dict[str, Any],
187
+ actor_context: Optional[Dict[str, str]] = None,
173
188
  timeout: float = 15.0,
174
189
  ) -> Dict[str, Any]:
175
190
  """Register a recipe type. ``POST /api/recipe/types/register``.
@@ -184,5 +199,6 @@ class RecipesNamespace:
184
199
  "schema_version": schema_version,
185
200
  "json_schema": json_schema,
186
201
  },
202
+ actor_context=actor_context,
187
203
  timeout=timeout,
188
204
  )