dominus-sdk-python 6.0.0__tar.gz → 6.1.0__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-6.0.0 → dominus_sdk_python-6.1.0}/PKG-INFO +7 -3
  2. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/README.md +5 -0
  3. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/dominus/__init__.py +5 -1
  4. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/dominus/namespaces/__init__.py +4 -0
  5. dominus_sdk_python-6.1.0/dominus/namespaces/coder.py +221 -0
  6. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/dominus/namespaces/logs.py +5 -0
  7. dominus_sdk_python-6.1.0/dominus/namespaces/platform.py +130 -0
  8. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/dominus/start.py +11 -3
  9. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/dominus_sdk_python.egg-info/PKG-INFO +7 -3
  10. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/dominus_sdk_python.egg-info/SOURCES.txt +3 -0
  11. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/pyproject.toml +3 -4
  12. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/tests/test_logs.py +2 -0
  13. dominus_sdk_python-6.1.0/tests/test_platform_coder_namespaces.py +118 -0
  14. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/tests/test_public_exports.py +6 -0
  15. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/dominus/config/__init__.py +0 -0
  16. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/dominus/config/endpoints.py +0 -0
  17. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/dominus/errors.py +0 -0
  18. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/dominus/helpers/__init__.py +0 -0
  19. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/dominus/helpers/auth.py +0 -0
  20. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/dominus/helpers/cache.py +0 -0
  21. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/dominus/helpers/console_capture.py +0 -0
  22. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/dominus/helpers/core.py +0 -0
  23. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/dominus/helpers/crypto.py +0 -0
  24. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/dominus/helpers/sse.py +0 -0
  25. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/dominus/helpers/trace.py +0 -0
  26. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/dominus/namespaces/admin.py +0 -0
  27. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/dominus/namespaces/ai.py +0 -0
  28. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/dominus/namespaces/artifacts.py +0 -0
  29. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/dominus/namespaces/auth.py +0 -0
  30. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/dominus/namespaces/authority.py +0 -0
  31. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/dominus/namespaces/browser.py +0 -0
  32. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/dominus/namespaces/courier.py +0 -0
  33. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/dominus/namespaces/db.py +0 -0
  34. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/dominus/namespaces/ddl.py +0 -0
  35. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/dominus/namespaces/deployer.py +0 -0
  36. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/dominus/namespaces/fastapi.py +0 -0
  37. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/dominus/namespaces/files.py +0 -0
  38. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/dominus/namespaces/health.py +0 -0
  39. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/dominus/namespaces/jobs.py +0 -0
  40. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/dominus/namespaces/portal.py +0 -0
  41. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/dominus/namespaces/processor.py +0 -0
  42. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/dominus/namespaces/recipes.py +0 -0
  43. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/dominus/namespaces/redis.py +0 -0
  44. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/dominus/namespaces/secrets.py +0 -0
  45. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/dominus/namespaces/secure.py +0 -0
  46. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/dominus/namespaces/stash.py +0 -0
  47. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/dominus/namespaces/sync.py +0 -0
  48. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/dominus/namespaces/warden.py +0 -0
  49. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/dominus/namespaces/workflow.py +0 -0
  50. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/dominus/services/__init__.py +0 -0
  51. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/dominus_sdk_python.egg-info/dependency_links.txt +0 -0
  52. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/dominus_sdk_python.egg-info/requires.txt +0 -0
  53. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/dominus_sdk_python.egg-info/top_level.txt +0 -0
  54. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/setup.cfg +0 -0
  55. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/tests/test_auth.py +0 -0
  56. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/tests/test_authority_public_vocabulary.py +0 -0
  57. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/tests/test_browser_namespace.py +0 -0
  58. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/tests/test_control_plane_namespaces.py +0 -0
  59. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/tests/test_errors.py +0 -0
  60. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/tests/test_flat_commands.py +0 -0
  61. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/tests/test_health.py +0 -0
  62. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/tests/test_provisioning_parity.py +0 -0
  63. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/tests/test_recipes_namespace.py +0 -0
  64. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/tests/test_transport_compat.py +0 -0
  65. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/tests/test_workflow_lifecycle.py +0 -0
  66. {dominus_sdk_python-6.0.0 → dominus_sdk_python-6.1.0}/tests/test_workflow_refs.py +0 -0
@@ -1,15 +1,14 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dominus-sdk-python
3
- Version: 6.0.0
3
+ Version: 6.1.0
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
@@ -90,6 +89,11 @@ logs_archive = await dominus.logs.get_archive_status(
90
89
  include_buffer=True,
91
90
  limit=5,
92
91
  )
92
+ machine_logs = await dominus.logs.tail(
93
+ machine_id="mach-abc-123",
94
+ since="2026-04-11T08:33:00Z",
95
+ level="error",
96
+ )
93
97
 
94
98
  # Archive maintenance is explicit and dry-run first.
95
99
  verify = await dominus.authority.verify_timeline_archive_manifests(
@@ -56,6 +56,11 @@ logs_archive = await dominus.logs.get_archive_status(
56
56
  include_buffer=True,
57
57
  limit=5,
58
58
  )
59
+ machine_logs = await dominus.logs.tail(
60
+ machine_id="mach-abc-123",
61
+ since="2026-04-11T08:33:00Z",
62
+ level="error",
63
+ )
59
64
 
60
65
  # Archive maintenance is explicit and dry-run first.
61
66
  verify = await dominus.authority.verify_timeline_archive_manifests(
@@ -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__ = "6.0.0"
167
+ __version__ = "6.1.0"
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",
@@ -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
+ )
@@ -335,6 +335,7 @@ class LogsNamespace:
335
335
  deploy_id: Optional[str] = None,
336
336
  installation_id: Optional[str] = None,
337
337
  artifact_ref: Optional[str] = None,
338
+ machine_id: Optional[str] = None,
338
339
  ) -> List[Dict[str, Any]]:
339
340
  """Tail recent logs from the Logs Worker.
340
341
 
@@ -352,6 +353,8 @@ class LogsNamespace:
352
353
  deploy_id: Optional deploy authority id filter
353
354
  installation_id: Optional managed-client installation id filter
354
355
  artifact_ref: Optional artifact ref filter
356
+ machine_id: Optional filter for events whose attrs.machine_id matches.
357
+ Used by per-machine log-tail UIs.
355
358
 
356
359
  Returns:
357
360
  List of log event dicts
@@ -385,6 +388,8 @@ class LogsNamespace:
385
388
  params["installation_id"] = installation_id
386
389
  if artifact_ref:
387
390
  params["artifact_ref"] = artifact_ref
391
+ if machine_id:
392
+ params["machine_id"] = machine_id
388
393
  qs = urlencode(params)
389
394
  endpoint = f"/api/logs/tail?{qs}" if qs else "/api/logs/tail"
390
395
 
@@ -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
+ )
@@ -179,8 +179,12 @@ class Dominus:
179
179
  # Thin operator control-plane namespaces (Mothership / MCP parity)
180
180
  from .namespaces.deployer import DeployerNamespace
181
181
  from .namespaces.warden import WardenNamespace
182
+ from .namespaces.platform import PlatformNamespace
183
+ from .namespaces.coder import CoderNamespace
182
184
  self.deployer = DeployerNamespace(self)
183
185
  self.warden = WardenNamespace(self)
186
+ self.platform = PlatformNamespace(self)
187
+ self.coder = CoderNamespace(self)
184
188
 
185
189
  # Stash worker (per-scope durable items: credentials + configs)
186
190
  from .namespaces.stash import StashNamespace
@@ -529,13 +533,17 @@ class Dominus:
529
533
  if not path.startswith("/"):
530
534
  path = f"/{path}"
531
535
 
532
- is_base64 = (
536
+ encode_base64 = (
533
537
  "/svc/warden/secrets" in path
534
538
  or "/svc/secrets/" in path
535
539
  or "/svc/admin/" in path
540
+ or "/svc/platform/" in path
541
+ or "/svc/coder/" in path
536
542
  )
537
543
  decode_base64 = (
538
- is_base64
544
+ "/svc/warden/secrets" in path
545
+ or "/svc/secrets/" in path
546
+ or "/svc/admin/" in path
539
547
  or "/svc/database/" in path
540
548
  or "/svc/sync/" in path
541
549
  or "/svc/authority/" in path
@@ -555,7 +563,7 @@ class Dominus:
555
563
  request_content = None
556
564
  request_json = None
557
565
  if not is_get and body is not None:
558
- if is_base64:
566
+ if encode_base64:
559
567
  req_headers["Content-Type"] = "text/plain"
560
568
  request_content = base64.b64encode(json.dumps(body).encode()).decode()
561
569
  else:
@@ -1,15 +1,14 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dominus-sdk-python
3
- Version: 6.0.0
3
+ Version: 6.1.0
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
@@ -90,6 +89,11 @@ logs_archive = await dominus.logs.get_archive_status(
90
89
  include_buffer=True,
91
90
  limit=5,
92
91
  )
92
+ machine_logs = await dominus.logs.tail(
93
+ machine_id="mach-abc-123",
94
+ since="2026-04-11T08:33:00Z",
95
+ level="error",
96
+ )
93
97
 
94
98
  # Archive maintenance is explicit and dry-run first.
95
99
  verify = await dominus.authority.verify_timeline_archive_manifests(
@@ -20,6 +20,7 @@ dominus/namespaces/artifacts.py
20
20
  dominus/namespaces/auth.py
21
21
  dominus/namespaces/authority.py
22
22
  dominus/namespaces/browser.py
23
+ dominus/namespaces/coder.py
23
24
  dominus/namespaces/courier.py
24
25
  dominus/namespaces/db.py
25
26
  dominus/namespaces/ddl.py
@@ -29,6 +30,7 @@ dominus/namespaces/files.py
29
30
  dominus/namespaces/health.py
30
31
  dominus/namespaces/jobs.py
31
32
  dominus/namespaces/logs.py
33
+ dominus/namespaces/platform.py
32
34
  dominus/namespaces/portal.py
33
35
  dominus/namespaces/processor.py
34
36
  dominus/namespaces/recipes.py
@@ -53,6 +55,7 @@ tests/test_errors.py
53
55
  tests/test_flat_commands.py
54
56
  tests/test_health.py
55
57
  tests/test_logs.py
58
+ tests/test_platform_coder_namespaces.py
56
59
  tests/test_provisioning_parity.py
57
60
  tests/test_public_exports.py
58
61
  tests/test_recipes_namespace.py
@@ -1,13 +1,13 @@
1
1
  [build-system]
2
- requires = ["setuptools>=61.0", "wheel"]
2
+ requires = ["setuptools>=77.0", "wheel"]
3
3
  build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "dominus-sdk-python"
7
- version = "6.0.0"
7
+ version = "6.1.0"
8
8
  description = "Python SDK for the Dominus gateway-first platform"
9
9
  readme = "README.md"
10
- license = {text = "Proprietary"}
10
+ license = "LicenseRef-Proprietary"
11
11
  requires-python = ">=3.9"
12
12
  authors = [
13
13
  {name = "CareBridge Systems", email = "dev@carebridge.io"}
@@ -16,7 +16,6 @@ keywords = ["dominus", "carebridge", "sdk", "gateway", "api", "async"]
16
16
  classifiers = [
17
17
  "Development Status :: 4 - Beta",
18
18
  "Intended Audience :: Developers",
19
- "License :: Other/Proprietary License",
20
19
  "Operating System :: OS Independent",
21
20
  "Programming Language :: Python :: 3",
22
21
  "Programming Language :: Python :: 3.9",
@@ -56,6 +56,7 @@ async def test_logs_tail_forwards_business_filters():
56
56
  deploy_id="deploy-123",
57
57
  installation_id="inst-123",
58
58
  artifact_ref="ar://carebridge/summit/production/company/report_snapshot_current",
59
+ machine_id="mach-abc-123",
59
60
  )
60
61
 
61
62
  assert len(events) == 1
@@ -66,6 +67,7 @@ async def test_logs_tail_forwards_business_filters():
66
67
  "&subject=PCM47474562&run_id=run-123&deploy_id=deploy-123"
67
68
  "&installation_id=inst-123&artifact_ref="
68
69
  "ar%3A%2F%2Fcarebridge%2Fsummit%2Fproduction%2Fcompany%2Freport_snapshot_current"
70
+ "&machine_id=mach-abc-123"
69
71
  )
70
72
  assert client.calls[0]["method"] == "GET"
71
73
  assert client.calls[0]["use_gateway"] is True
@@ -0,0 +1,118 @@
1
+ import pytest
2
+
3
+ from dominus.namespaces.coder import CoderNamespace
4
+ from dominus.namespaces.platform import PlatformNamespace
5
+
6
+
7
+ class MockGatewayClient:
8
+ def __init__(self):
9
+ self.calls = []
10
+
11
+ async def gateway_fetch(self, path, **kwargs):
12
+ self.calls.append((path, kwargs))
13
+ return {"ok": True}
14
+
15
+
16
+ @pytest.mark.asyncio
17
+ async def test_platform_namespace_normalizes_helpers_onto_service_routes():
18
+ client = MockGatewayClient()
19
+ platform = PlatformNamespace(client)
20
+
21
+ await platform.health()
22
+ await platform.list_groups()
23
+ await platform.upsert_group({"slug": "dominus", "name": "Dominus"})
24
+ await platform.add_group_member("dominus", "user-1")
25
+ await platform.link_group_repository(
26
+ "dominus",
27
+ "carebridgesystems/dominus-platform-worker",
28
+ )
29
+ await platform.ensure_policy_decision(
30
+ {
31
+ "group": "dominus",
32
+ "repository": "carebridgesystems/dominus-platform-worker",
33
+ "metadata": {"safe": "ok"},
34
+ }
35
+ )
36
+ await platform.rehydrate_policy_decision("pd_123", {"run_id": "run_123"})
37
+
38
+ assert [(path, kwargs["method"]) for path, kwargs in client.calls] == [
39
+ ("/svc/platform/health", "GET"),
40
+ ("/svc/platform/groups", "GET"),
41
+ ("/svc/platform/groups", "POST"),
42
+ ("/svc/platform/groups/dominus/members", "POST"),
43
+ ("/svc/platform/groups/dominus/repositories", "POST"),
44
+ ("/svc/platform/policy/decisions/ensure", "POST"),
45
+ ("/svc/platform/policy/decisions/pd_123/rehydrate", "POST"),
46
+ ]
47
+ assert client.calls[2][1]["body"] == {"slug": "dominus", "name": "Dominus"}
48
+ assert client.calls[3][1]["body"] == {"subject_id": "user-1"}
49
+ assert client.calls[5][1]["body"] == {
50
+ "group": "dominus",
51
+ "repository": "carebridgesystems/dominus-platform-worker",
52
+ "metadata": {"safe": "ok"},
53
+ }
54
+
55
+
56
+ @pytest.mark.asyncio
57
+ async def test_platform_request_preserves_explicit_platform_paths():
58
+ client = MockGatewayClient()
59
+ platform = PlatformNamespace(client)
60
+
61
+ await platform.request("/api/platform/groups", method="GET")
62
+ await platform.request("/svc/platform/policy/decisions/pd_123", method="GET")
63
+
64
+ assert [path for path, _ in client.calls] == [
65
+ "/svc/platform/groups",
66
+ "/svc/platform/policy/decisions/pd_123",
67
+ ]
68
+
69
+
70
+ @pytest.mark.asyncio
71
+ async def test_coder_namespace_exposes_lifecycle_helpers_but_no_raw_shell():
72
+ client = MockGatewayClient()
73
+ coder = CoderNamespace(client)
74
+
75
+ await coder.ensure_run(
76
+ policy_decision_id="pd_123",
77
+ mode="async",
78
+ task_recipe_ref="recipe://coder-task-recipe-v1/fix-tests@head",
79
+ repository="carebridgesystems/dominus-platform-worker",
80
+ instructions="Fix failing tests",
81
+ )
82
+ await coder.list_runs(status="running", limit=10)
83
+ await coder.cancel_run("run_123", reason="operator requested")
84
+ await coder.retry_run("run_123", idempotency_key="retry-1")
85
+ await coder.nudge_run("run_123", metadata={"hint": "continue"})
86
+ await coder.get_run_summary("run_123")
87
+ await coder.get_run_artifacts("run_123")
88
+
89
+ assert [(path, kwargs["method"]) for path, kwargs in client.calls] == [
90
+ ("/svc/coder/runs/ensure", "POST"),
91
+ ("/svc/coder/runs?status=running&limit=10", "GET"),
92
+ ("/svc/coder/runs/run_123/cancel", "POST"),
93
+ ("/svc/coder/runs/run_123/retry", "POST"),
94
+ ("/svc/coder/runs/run_123/nudge", "POST"),
95
+ ("/svc/coder/runs/run_123/summary", "GET"),
96
+ ("/svc/coder/runs/run_123/artifacts", "GET"),
97
+ ]
98
+ assert client.calls[0][1]["body"] == {
99
+ "policy_decision_id": "pd_123",
100
+ "mode": "async",
101
+ "task_recipe_ref": "recipe://coder-task-recipe-v1/fix-tests@head",
102
+ "repository": "carebridgesystems/dominus-platform-worker",
103
+ "instructions": "Fix failing tests",
104
+ }
105
+ assert not hasattr(coder, "shell")
106
+ assert not hasattr(coder, "exec")
107
+ assert not hasattr(coder, "command")
108
+
109
+
110
+ @pytest.mark.asyncio
111
+ async def test_coder_ensure_run_requires_policy_decision_id():
112
+ client = MockGatewayClient()
113
+ coder = CoderNamespace(client)
114
+
115
+ with pytest.raises(ValueError, match="policy_decision_id"):
116
+ await coder.ensure_run(policy_decision_id="")
117
+
118
+ assert client.calls == []
@@ -1,6 +1,8 @@
1
1
  from dominus import (
2
2
  BrowserNamespace,
3
+ CoderNamespace,
3
4
  DeployerNamespace,
5
+ PlatformNamespace,
4
6
  RecipesNamespace,
5
7
  WardenNamespace,
6
8
  gateway_circuit_breaker,
@@ -33,6 +35,8 @@ def test_top_level_exports_drop_delegate_alias():
33
35
  assert WardenNamespace is not None
34
36
  assert BrowserNamespace is not None
35
37
  assert RecipesNamespace is not None
38
+ assert PlatformNamespace is not None
39
+ assert CoderNamespace is not None
36
40
 
37
41
 
38
42
  def test_singleton_exposes_browser_namespace():
@@ -40,3 +44,5 @@ def test_singleton_exposes_browser_namespace():
40
44
 
41
45
  assert callable(dominus.browser.get_health)
42
46
  assert callable(dominus.recipes.list_types)
47
+ assert callable(dominus.platform.ensure_policy_decision)
48
+ assert callable(dominus.coder.ensure_run)