dominus-sdk-python 6.1.0__tar.gz → 6.1.2__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.
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/PKG-INFO +18 -2
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/README.md +17 -1
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus/__init__.py +3 -1
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus/namespaces/coder.py +56 -5
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus/namespaces/platform.py +27 -2
- dominus_sdk_python-6.1.2/dominus/namespaces/publisher.py +300 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus/start.py +3 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus_sdk_python.egg-info/PKG-INFO +18 -2
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus_sdk_python.egg-info/SOURCES.txt +2 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/pyproject.toml +1 -1
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/tests/test_platform_coder_namespaces.py +25 -1
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/tests/test_public_exports.py +3 -0
- dominus_sdk_python-6.1.2/tests/test_publisher_namespace.py +82 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus/config/__init__.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus/config/endpoints.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus/errors.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus/helpers/__init__.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus/helpers/auth.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus/helpers/cache.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus/helpers/console_capture.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus/helpers/core.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus/helpers/crypto.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus/helpers/sse.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus/helpers/trace.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus/namespaces/__init__.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus/namespaces/admin.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus/namespaces/ai.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus/namespaces/artifacts.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus/namespaces/auth.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus/namespaces/authority.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus/namespaces/browser.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus/namespaces/courier.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus/namespaces/db.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus/namespaces/ddl.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus/namespaces/deployer.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus/namespaces/fastapi.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus/namespaces/files.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus/namespaces/health.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus/namespaces/jobs.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus/namespaces/logs.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus/namespaces/portal.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus/namespaces/processor.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus/namespaces/recipes.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus/namespaces/redis.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus/namespaces/secrets.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus/namespaces/secure.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus/namespaces/stash.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus/namespaces/sync.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus/namespaces/warden.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus/namespaces/workflow.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus/services/__init__.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus_sdk_python.egg-info/dependency_links.txt +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus_sdk_python.egg-info/requires.txt +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus_sdk_python.egg-info/top_level.txt +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/setup.cfg +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/tests/test_auth.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/tests/test_authority_public_vocabulary.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/tests/test_browser_namespace.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/tests/test_control_plane_namespaces.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/tests/test_errors.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/tests/test_flat_commands.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/tests/test_health.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/tests/test_logs.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/tests/test_provisioning_parity.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/tests/test_recipes_namespace.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/tests/test_transport_compat.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/tests/test_workflow_lifecycle.py +0 -0
- {dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/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: 6.1.
|
|
3
|
+
Version: 6.1.2
|
|
4
4
|
Summary: Python SDK for the Dominus gateway-first platform
|
|
5
5
|
Author-email: CareBridge Systems <dev@carebridge.io>
|
|
6
6
|
License-Expression: LicenseRef-Proprietary
|
|
@@ -42,7 +42,7 @@ Async Python SDK for the Dominus gateway-first service plane.
|
|
|
42
42
|
- Gateway-scoped client mode for MCP and other user-JWT sessions
|
|
43
43
|
- Transport compatibility for wrapped `{success,data}` responses and unwrapped Warden/control-plane success objects
|
|
44
44
|
- Local helpers for JWT verification, trace propagation, retries, and console capture
|
|
45
|
-
- Current package version: `6.
|
|
45
|
+
- Current package version: `6.1.1`
|
|
46
46
|
|
|
47
47
|
## Install
|
|
48
48
|
|
|
@@ -94,6 +94,20 @@ machine_logs = await dominus.logs.tail(
|
|
|
94
94
|
since="2026-04-11T08:33:00Z",
|
|
95
95
|
level="error",
|
|
96
96
|
)
|
|
97
|
+
policy = await dominus.platform.ensure_policy_decision(
|
|
98
|
+
{
|
|
99
|
+
"group": "dominus",
|
|
100
|
+
"repository": "carebridgesystems/dominus-platform-worker",
|
|
101
|
+
},
|
|
102
|
+
actor_context={"type": "user", "id": "operator-1"},
|
|
103
|
+
)
|
|
104
|
+
coder_run = await dominus.coder.ensure_run(
|
|
105
|
+
policy_decision_id=policy["data"]["policy_decision"]["decision_id"],
|
|
106
|
+
workflow_recipe_ref="recipe://workflow-recipe-v1/coder-feature@head",
|
|
107
|
+
repository="carebridgesystems/dominus-platform-worker",
|
|
108
|
+
instructions="Fix failing tests",
|
|
109
|
+
actor_context={"type": "user", "id": "operator-1"},
|
|
110
|
+
)
|
|
97
111
|
|
|
98
112
|
# Archive maintenance is explicit and dry-run first.
|
|
99
113
|
verify = await dominus.authority.verify_timeline_archive_manifests(
|
|
@@ -206,6 +220,8 @@ JWT and selected scope headers directly through Gateway.
|
|
|
206
220
|
| `browser` | Browser Worker | Browser run health, ensure/start/status/result/retry/nudge/cancel/timeline/dossier |
|
|
207
221
|
| `deployer` | Deployer | Thin operator control-plane request surface |
|
|
208
222
|
| `warden` | Warden | Thin operator control-plane request surface |
|
|
223
|
+
| `platform` | Platform Worker | Group/repository policy decisions with actor attribution |
|
|
224
|
+
| `coder` | Coder Runtime | Policy-bound Coder run lifecycle with workflow/pipeline recipe launch sources |
|
|
209
225
|
| `fastapi` | Local decorators | `@jwt`, `@psk`, `@scopes(...)` |
|
|
210
226
|
|
|
211
227
|
## Root Shortcuts
|
|
@@ -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: `6.
|
|
12
|
+
- Current package version: `6.1.1`
|
|
13
13
|
|
|
14
14
|
## Install
|
|
15
15
|
|
|
@@ -61,6 +61,20 @@ machine_logs = await dominus.logs.tail(
|
|
|
61
61
|
since="2026-04-11T08:33:00Z",
|
|
62
62
|
level="error",
|
|
63
63
|
)
|
|
64
|
+
policy = await dominus.platform.ensure_policy_decision(
|
|
65
|
+
{
|
|
66
|
+
"group": "dominus",
|
|
67
|
+
"repository": "carebridgesystems/dominus-platform-worker",
|
|
68
|
+
},
|
|
69
|
+
actor_context={"type": "user", "id": "operator-1"},
|
|
70
|
+
)
|
|
71
|
+
coder_run = await dominus.coder.ensure_run(
|
|
72
|
+
policy_decision_id=policy["data"]["policy_decision"]["decision_id"],
|
|
73
|
+
workflow_recipe_ref="recipe://workflow-recipe-v1/coder-feature@head",
|
|
74
|
+
repository="carebridgesystems/dominus-platform-worker",
|
|
75
|
+
instructions="Fix failing tests",
|
|
76
|
+
actor_context={"type": "user", "id": "operator-1"},
|
|
77
|
+
)
|
|
64
78
|
|
|
65
79
|
# Archive maintenance is explicit and dry-run first.
|
|
66
80
|
verify = await dominus.authority.verify_timeline_archive_manifests(
|
|
@@ -173,6 +187,8 @@ JWT and selected scope headers directly through Gateway.
|
|
|
173
187
|
| `browser` | Browser Worker | Browser run health, ensure/start/status/result/retry/nudge/cancel/timeline/dossier |
|
|
174
188
|
| `deployer` | Deployer | Thin operator control-plane request surface |
|
|
175
189
|
| `warden` | Warden | Thin operator control-plane request surface |
|
|
190
|
+
| `platform` | Platform Worker | Group/repository policy decisions with actor attribution |
|
|
191
|
+
| `coder` | Coder Runtime | Policy-bound Coder run lifecycle with workflow/pipeline recipe launch sources |
|
|
176
192
|
| `fastapi` | Local decorators | `@jwt`, `@psk`, `@scopes(...)` |
|
|
177
193
|
|
|
178
194
|
## Root Shortcuts
|
|
@@ -114,6 +114,7 @@ from .namespaces.deployer import DeployerNamespace
|
|
|
114
114
|
from .namespaces.warden import WardenNamespace
|
|
115
115
|
from .namespaces.platform import PlatformNamespace
|
|
116
116
|
from .namespaces.coder import CoderNamespace
|
|
117
|
+
from .namespaces.publisher import PublisherNamespace
|
|
117
118
|
|
|
118
119
|
# Export AI namespace for agent-runtime operations
|
|
119
120
|
from .namespaces.ai import (
|
|
@@ -164,7 +165,7 @@ from .errors import (
|
|
|
164
165
|
TimeoutError as DominusTimeoutError,
|
|
165
166
|
)
|
|
166
167
|
|
|
167
|
-
__version__ = "6.1.
|
|
168
|
+
__version__ = "6.1.2"
|
|
168
169
|
__all__ = [
|
|
169
170
|
# Main SDK instance
|
|
170
171
|
"dominus",
|
|
@@ -217,6 +218,7 @@ __all__ = [
|
|
|
217
218
|
"WardenNamespace",
|
|
218
219
|
"PlatformNamespace",
|
|
219
220
|
"CoderNamespace",
|
|
221
|
+
"PublisherNamespace",
|
|
220
222
|
# AI namespace for agent-runtime operations
|
|
221
223
|
"AiNamespace",
|
|
222
224
|
"RagSubNamespace",
|
|
@@ -54,6 +54,26 @@ def _mutation_body(
|
|
|
54
54
|
)
|
|
55
55
|
|
|
56
56
|
|
|
57
|
+
def _actor_headers(actor_context: Optional[Dict[str, str]]) -> Optional[Dict[str, str]]:
|
|
58
|
+
if not actor_context:
|
|
59
|
+
return None
|
|
60
|
+
actor_type = str(actor_context.get("type") or actor_context.get("actor_type") or "").strip()
|
|
61
|
+
actor_id = str(actor_context.get("id") or actor_context.get("actor_id") or "").strip()
|
|
62
|
+
if not actor_type or not actor_id:
|
|
63
|
+
return None
|
|
64
|
+
return {"X-Actor-Type": actor_type, "X-Actor-Id": actor_id}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _assert_one_launch_source(workflow_recipe_ref: Optional[str], pipeline_recipe_ref: Optional[str]) -> None:
|
|
68
|
+
present = [
|
|
69
|
+
ref
|
|
70
|
+
for ref in (workflow_recipe_ref, pipeline_recipe_ref)
|
|
71
|
+
if isinstance(ref, str) and ref.strip()
|
|
72
|
+
]
|
|
73
|
+
if len(present) != 1:
|
|
74
|
+
raise ValueError("ensure_run requires exactly one of workflow_recipe_ref or pipeline_recipe_ref")
|
|
75
|
+
|
|
76
|
+
|
|
57
77
|
class CoderNamespace:
|
|
58
78
|
"""Coder run lifecycle helpers."""
|
|
59
79
|
|
|
@@ -101,9 +121,11 @@ class CoderNamespace:
|
|
|
101
121
|
instructions: Optional[str] = None,
|
|
102
122
|
inputs: Optional[Dict[str, Any]] = None,
|
|
103
123
|
metadata: Optional[Dict[str, Any]] = None,
|
|
124
|
+
actor_context: Optional[Dict[str, str]] = None,
|
|
104
125
|
) -> Dict[str, Any]:
|
|
105
126
|
if not policy_decision_id or policy_decision_id.strip() == "":
|
|
106
127
|
raise ValueError("ensure_run requires policy_decision_id")
|
|
128
|
+
_assert_one_launch_source(workflow_recipe_ref, pipeline_recipe_ref)
|
|
107
129
|
|
|
108
130
|
body = _compact(
|
|
109
131
|
{
|
|
@@ -128,6 +150,7 @@ class CoderNamespace:
|
|
|
128
150
|
"/runs/ensure",
|
|
129
151
|
method="POST",
|
|
130
152
|
body=body,
|
|
153
|
+
headers=_actor_headers(actor_context),
|
|
131
154
|
timeout=600.0,
|
|
132
155
|
)
|
|
133
156
|
|
|
@@ -139,6 +162,7 @@ class CoderNamespace:
|
|
|
139
162
|
repository: Optional[str] = None,
|
|
140
163
|
limit: Optional[int] = None,
|
|
141
164
|
offset: Optional[int] = None,
|
|
165
|
+
actor_context: Optional[Dict[str, str]] = None,
|
|
142
166
|
) -> Dict[str, Any]:
|
|
143
167
|
qs = _query_string(
|
|
144
168
|
{
|
|
@@ -149,10 +173,19 @@ class CoderNamespace:
|
|
|
149
173
|
"offset": offset,
|
|
150
174
|
}
|
|
151
175
|
)
|
|
152
|
-
return await self.request(f"/runs{qs}", method="GET")
|
|
176
|
+
return await self.request(f"/runs{qs}", method="GET", headers=_actor_headers(actor_context))
|
|
153
177
|
|
|
154
|
-
async def get_run(
|
|
155
|
-
|
|
178
|
+
async def get_run(
|
|
179
|
+
self,
|
|
180
|
+
run_id: str,
|
|
181
|
+
*,
|
|
182
|
+
actor_context: Optional[Dict[str, str]] = None,
|
|
183
|
+
) -> Dict[str, Any]:
|
|
184
|
+
return await self.request(
|
|
185
|
+
f"/runs/{quote(run_id, safe='')}",
|
|
186
|
+
method="GET",
|
|
187
|
+
headers=_actor_headers(actor_context),
|
|
188
|
+
)
|
|
156
189
|
|
|
157
190
|
async def cancel_run(
|
|
158
191
|
self,
|
|
@@ -161,6 +194,7 @@ class CoderNamespace:
|
|
|
161
194
|
reason: Optional[str] = None,
|
|
162
195
|
idempotency_key: Optional[str] = None,
|
|
163
196
|
metadata: Optional[Dict[str, Any]] = None,
|
|
197
|
+
actor_context: Optional[Dict[str, str]] = None,
|
|
164
198
|
) -> Dict[str, Any]:
|
|
165
199
|
return await self.request(
|
|
166
200
|
f"/runs/{quote(run_id, safe='')}/cancel",
|
|
@@ -170,6 +204,7 @@ class CoderNamespace:
|
|
|
170
204
|
idempotency_key=idempotency_key,
|
|
171
205
|
metadata=metadata,
|
|
172
206
|
),
|
|
207
|
+
headers=_actor_headers(actor_context),
|
|
173
208
|
)
|
|
174
209
|
|
|
175
210
|
async def retry_run(
|
|
@@ -179,6 +214,7 @@ class CoderNamespace:
|
|
|
179
214
|
reason: Optional[str] = None,
|
|
180
215
|
idempotency_key: Optional[str] = None,
|
|
181
216
|
metadata: Optional[Dict[str, Any]] = None,
|
|
217
|
+
actor_context: Optional[Dict[str, str]] = None,
|
|
182
218
|
) -> Dict[str, Any]:
|
|
183
219
|
return await self.request(
|
|
184
220
|
f"/runs/{quote(run_id, safe='')}/retry",
|
|
@@ -188,6 +224,7 @@ class CoderNamespace:
|
|
|
188
224
|
idempotency_key=idempotency_key,
|
|
189
225
|
metadata=metadata,
|
|
190
226
|
),
|
|
227
|
+
headers=_actor_headers(actor_context),
|
|
191
228
|
)
|
|
192
229
|
|
|
193
230
|
async def nudge_run(
|
|
@@ -197,6 +234,7 @@ class CoderNamespace:
|
|
|
197
234
|
reason: Optional[str] = None,
|
|
198
235
|
idempotency_key: Optional[str] = None,
|
|
199
236
|
metadata: Optional[Dict[str, Any]] = None,
|
|
237
|
+
actor_context: Optional[Dict[str, str]] = None,
|
|
200
238
|
) -> Dict[str, Any]:
|
|
201
239
|
return await self.request(
|
|
202
240
|
f"/runs/{quote(run_id, safe='')}/nudge",
|
|
@@ -206,16 +244,29 @@ class CoderNamespace:
|
|
|
206
244
|
idempotency_key=idempotency_key,
|
|
207
245
|
metadata=metadata,
|
|
208
246
|
),
|
|
247
|
+
headers=_actor_headers(actor_context),
|
|
209
248
|
)
|
|
210
249
|
|
|
211
|
-
async def get_run_summary(
|
|
250
|
+
async def get_run_summary(
|
|
251
|
+
self,
|
|
252
|
+
run_id: str,
|
|
253
|
+
*,
|
|
254
|
+
actor_context: Optional[Dict[str, str]] = None,
|
|
255
|
+
) -> Dict[str, Any]:
|
|
212
256
|
return await self.request(
|
|
213
257
|
f"/runs/{quote(run_id, safe='')}/summary",
|
|
214
258
|
method="GET",
|
|
259
|
+
headers=_actor_headers(actor_context),
|
|
215
260
|
)
|
|
216
261
|
|
|
217
|
-
async def get_run_artifacts(
|
|
262
|
+
async def get_run_artifacts(
|
|
263
|
+
self,
|
|
264
|
+
run_id: str,
|
|
265
|
+
*,
|
|
266
|
+
actor_context: Optional[Dict[str, str]] = None,
|
|
267
|
+
) -> Dict[str, Any]:
|
|
218
268
|
return await self.request(
|
|
219
269
|
f"/runs/{quote(run_id, safe='')}/artifacts",
|
|
220
270
|
method="GET",
|
|
271
|
+
headers=_actor_headers(actor_context),
|
|
221
272
|
)
|
|
@@ -23,6 +23,16 @@ def _normalize_platform_path(path: str) -> str:
|
|
|
23
23
|
return f"/svc/platform{normalized}"
|
|
24
24
|
|
|
25
25
|
|
|
26
|
+
def _actor_headers(actor_context: Optional[Dict[str, str]]) -> Optional[Dict[str, str]]:
|
|
27
|
+
if not actor_context:
|
|
28
|
+
return None
|
|
29
|
+
actor_type = str(actor_context.get("type") or actor_context.get("actor_type") or "").strip()
|
|
30
|
+
actor_id = str(actor_context.get("id") or actor_context.get("actor_id") or "").strip()
|
|
31
|
+
if not actor_type or not actor_id:
|
|
32
|
+
return None
|
|
33
|
+
return {"X-Actor-Type": actor_type, "X-Actor-Id": actor_id}
|
|
34
|
+
|
|
35
|
+
|
|
26
36
|
class PlatformNamespace:
|
|
27
37
|
"""Platform group/repository policy helpers."""
|
|
28
38
|
|
|
@@ -105,26 +115,41 @@ class PlatformNamespace:
|
|
|
105
115
|
body=repository,
|
|
106
116
|
)
|
|
107
117
|
|
|
108
|
-
async def ensure_policy_decision(
|
|
118
|
+
async def ensure_policy_decision(
|
|
119
|
+
self,
|
|
120
|
+
options: Dict[str, Any],
|
|
121
|
+
*,
|
|
122
|
+
actor_context: Optional[Dict[str, str]] = None,
|
|
123
|
+
) -> Dict[str, Any]:
|
|
109
124
|
return await self.request(
|
|
110
125
|
"/policy/decisions/ensure",
|
|
111
126
|
method="POST",
|
|
112
127
|
body=options,
|
|
128
|
+
headers=_actor_headers(actor_context),
|
|
113
129
|
)
|
|
114
130
|
|
|
115
|
-
async def get_policy_decision(
|
|
131
|
+
async def get_policy_decision(
|
|
132
|
+
self,
|
|
133
|
+
decision_id: str,
|
|
134
|
+
*,
|
|
135
|
+
actor_context: Optional[Dict[str, str]] = None,
|
|
136
|
+
) -> Dict[str, Any]:
|
|
116
137
|
return await self.request(
|
|
117
138
|
f"/policy/decisions/{quote(decision_id, safe='')}",
|
|
118
139
|
method="GET",
|
|
140
|
+
headers=_actor_headers(actor_context),
|
|
119
141
|
)
|
|
120
142
|
|
|
121
143
|
async def rehydrate_policy_decision(
|
|
122
144
|
self,
|
|
123
145
|
decision_id: str,
|
|
124
146
|
options: Optional[Dict[str, Any]] = None,
|
|
147
|
+
*,
|
|
148
|
+
actor_context: Optional[Dict[str, str]] = None,
|
|
125
149
|
) -> Dict[str, Any]:
|
|
126
150
|
return await self.request(
|
|
127
151
|
f"/policy/decisions/{quote(decision_id, safe='')}/rehydrate",
|
|
128
152
|
method="POST",
|
|
129
153
|
body=options or {},
|
|
154
|
+
headers=_actor_headers(actor_context),
|
|
130
155
|
)
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
"""Publisher namespace.
|
|
2
|
+
|
|
3
|
+
Build-artifact lifecycle, signing, release records, channel pins, and resolver
|
|
4
|
+
reads for dominus-publisher. This surface intentionally does not expose raw
|
|
5
|
+
runner credentials or direct byte upload shortcuts.
|
|
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_publisher_path(path: str) -> str:
|
|
33
|
+
trimmed = path.strip()
|
|
34
|
+
normalized = trimmed if trimmed.startswith("/") else f"/{trimmed}"
|
|
35
|
+
if normalized == "/svc/publisher" or normalized.startswith("/svc/publisher/"):
|
|
36
|
+
return normalized
|
|
37
|
+
if normalized == "/api/publisher" or normalized.startswith("/api/publisher/"):
|
|
38
|
+
return normalized.replace("/api/", "/svc/", 1)
|
|
39
|
+
return f"/svc/publisher{normalized}"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _actor_headers(actor_context: Optional[Dict[str, str]]) -> Optional[Dict[str, str]]:
|
|
43
|
+
if not actor_context:
|
|
44
|
+
return None
|
|
45
|
+
actor_type = str(actor_context.get("type") or actor_context.get("actor_type") or "").strip()
|
|
46
|
+
actor_id = str(actor_context.get("id") or actor_context.get("actor_id") or "").strip()
|
|
47
|
+
if not actor_type or not actor_id:
|
|
48
|
+
return None
|
|
49
|
+
return {"X-Actor-Type": actor_type, "X-Actor-Id": actor_id}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _mutation_body(
|
|
53
|
+
*,
|
|
54
|
+
reason: Optional[str] = None,
|
|
55
|
+
idempotency_key: Optional[str] = None,
|
|
56
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
57
|
+
) -> Dict[str, Any]:
|
|
58
|
+
return _compact(
|
|
59
|
+
{
|
|
60
|
+
"reason": reason,
|
|
61
|
+
"idempotency_key": idempotency_key,
|
|
62
|
+
"metadata": metadata,
|
|
63
|
+
}
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class PublisherNamespace:
|
|
68
|
+
"""Publisher build-artifact lifecycle helpers."""
|
|
69
|
+
|
|
70
|
+
def __init__(self, client: "Dominus"):
|
|
71
|
+
self._client = client
|
|
72
|
+
|
|
73
|
+
async def request(
|
|
74
|
+
self,
|
|
75
|
+
path: str,
|
|
76
|
+
*,
|
|
77
|
+
method: str = "GET",
|
|
78
|
+
body: Optional[Dict[str, Any]] = None,
|
|
79
|
+
headers: Optional[Dict[str, str]] = None,
|
|
80
|
+
timeout: float = 30.0,
|
|
81
|
+
) -> Any:
|
|
82
|
+
return await self._client.gateway_fetch(
|
|
83
|
+
_normalize_publisher_path(path),
|
|
84
|
+
method=method,
|
|
85
|
+
body=body,
|
|
86
|
+
headers=headers,
|
|
87
|
+
timeout=timeout,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
async def health(self) -> Dict[str, Any]:
|
|
91
|
+
return await self.request("/health", method="GET")
|
|
92
|
+
|
|
93
|
+
async def ready(self) -> Dict[str, Any]:
|
|
94
|
+
return await self.request("/ready", method="GET")
|
|
95
|
+
|
|
96
|
+
async def list_builds(
|
|
97
|
+
self,
|
|
98
|
+
*,
|
|
99
|
+
actor_context: Optional[Dict[str, str]] = None,
|
|
100
|
+
) -> Dict[str, Any]:
|
|
101
|
+
return await self.request("/builds", method="GET", headers=_actor_headers(actor_context))
|
|
102
|
+
|
|
103
|
+
async def ensure_build(
|
|
104
|
+
self,
|
|
105
|
+
payload: Dict[str, Any],
|
|
106
|
+
*,
|
|
107
|
+
actor_context: Optional[Dict[str, str]] = None,
|
|
108
|
+
) -> Dict[str, Any]:
|
|
109
|
+
return await self.request(
|
|
110
|
+
"/builds/ensure",
|
|
111
|
+
method="POST",
|
|
112
|
+
body=payload,
|
|
113
|
+
headers=_actor_headers(actor_context),
|
|
114
|
+
timeout=600.0,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
async def get_build(
|
|
118
|
+
self,
|
|
119
|
+
build_id: str,
|
|
120
|
+
*,
|
|
121
|
+
actor_context: Optional[Dict[str, str]] = None,
|
|
122
|
+
) -> Dict[str, Any]:
|
|
123
|
+
return await self.request(
|
|
124
|
+
f"/builds/{quote(build_id, safe='')}",
|
|
125
|
+
method="GET",
|
|
126
|
+
headers=_actor_headers(actor_context),
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
async def retry_build(
|
|
130
|
+
self,
|
|
131
|
+
build_id: str,
|
|
132
|
+
*,
|
|
133
|
+
reason: Optional[str] = None,
|
|
134
|
+
idempotency_key: Optional[str] = None,
|
|
135
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
136
|
+
actor_context: Optional[Dict[str, str]] = None,
|
|
137
|
+
) -> Dict[str, Any]:
|
|
138
|
+
return await self.request(
|
|
139
|
+
f"/builds/{quote(build_id, safe='')}/retry",
|
|
140
|
+
method="POST",
|
|
141
|
+
body=_mutation_body(reason=reason, idempotency_key=idempotency_key, metadata=metadata),
|
|
142
|
+
headers=_actor_headers(actor_context),
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
async def cancel_build(
|
|
146
|
+
self,
|
|
147
|
+
build_id: str,
|
|
148
|
+
*,
|
|
149
|
+
reason: Optional[str] = None,
|
|
150
|
+
idempotency_key: Optional[str] = None,
|
|
151
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
152
|
+
actor_context: Optional[Dict[str, str]] = None,
|
|
153
|
+
) -> Dict[str, Any]:
|
|
154
|
+
return await self.request(
|
|
155
|
+
f"/builds/{quote(build_id, safe='')}/cancel",
|
|
156
|
+
method="POST",
|
|
157
|
+
body=_mutation_body(reason=reason, idempotency_key=idempotency_key, metadata=metadata),
|
|
158
|
+
headers=_actor_headers(actor_context),
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
async def evaluate_signing(
|
|
162
|
+
self,
|
|
163
|
+
payload: Dict[str, Any],
|
|
164
|
+
*,
|
|
165
|
+
actor_context: Optional[Dict[str, str]] = None,
|
|
166
|
+
) -> Dict[str, Any]:
|
|
167
|
+
return await self.request(
|
|
168
|
+
"/builds/signing/evaluate",
|
|
169
|
+
method="POST",
|
|
170
|
+
body=payload,
|
|
171
|
+
headers=_actor_headers(actor_context),
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
async def record_signing_evidence(
|
|
175
|
+
self,
|
|
176
|
+
payload: Dict[str, Any],
|
|
177
|
+
*,
|
|
178
|
+
actor_context: Optional[Dict[str, str]] = None,
|
|
179
|
+
) -> Dict[str, Any]:
|
|
180
|
+
return await self.request(
|
|
181
|
+
"/builds/signing/evidence",
|
|
182
|
+
method="POST",
|
|
183
|
+
body=payload,
|
|
184
|
+
headers=_actor_headers(actor_context),
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
async def list_artifacts(
|
|
188
|
+
self,
|
|
189
|
+
*,
|
|
190
|
+
actor_context: Optional[Dict[str, str]] = None,
|
|
191
|
+
) -> Dict[str, Any]:
|
|
192
|
+
return await self.request("/artifacts", method="GET", headers=_actor_headers(actor_context))
|
|
193
|
+
|
|
194
|
+
async def record_release(
|
|
195
|
+
self,
|
|
196
|
+
payload: Dict[str, Any],
|
|
197
|
+
*,
|
|
198
|
+
actor_context: Optional[Dict[str, str]] = None,
|
|
199
|
+
) -> Dict[str, Any]:
|
|
200
|
+
return await self.request(
|
|
201
|
+
"/artifacts/releases/record",
|
|
202
|
+
method="POST",
|
|
203
|
+
body=payload,
|
|
204
|
+
headers=_actor_headers(actor_context),
|
|
205
|
+
timeout=600.0,
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
async def resolve_artifact(
|
|
209
|
+
self,
|
|
210
|
+
artifact_key: str,
|
|
211
|
+
*,
|
|
212
|
+
selector: Optional[str] = None,
|
|
213
|
+
environment: Optional[str] = None,
|
|
214
|
+
target_app: Optional[str] = None,
|
|
215
|
+
architecture: Optional[str] = None,
|
|
216
|
+
runtime_family: Optional[str] = None,
|
|
217
|
+
actor_context: Optional[Dict[str, str]] = None,
|
|
218
|
+
) -> Dict[str, Any]:
|
|
219
|
+
qs = _query_string(
|
|
220
|
+
{
|
|
221
|
+
"selector": selector,
|
|
222
|
+
"environment": environment,
|
|
223
|
+
"target_app": target_app,
|
|
224
|
+
"architecture": architecture,
|
|
225
|
+
"runtime_family": runtime_family,
|
|
226
|
+
}
|
|
227
|
+
)
|
|
228
|
+
return await self.request(
|
|
229
|
+
f"/artifacts/{quote(artifact_key, safe='')}{qs}",
|
|
230
|
+
method="GET",
|
|
231
|
+
headers=_actor_headers(actor_context),
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
async def list_channels(
|
|
235
|
+
self,
|
|
236
|
+
*,
|
|
237
|
+
actor_context: Optional[Dict[str, str]] = None,
|
|
238
|
+
) -> Dict[str, Any]:
|
|
239
|
+
return await self.request("/channels", method="GET", headers=_actor_headers(actor_context))
|
|
240
|
+
|
|
241
|
+
async def get_channel(
|
|
242
|
+
self,
|
|
243
|
+
artifact_key: str,
|
|
244
|
+
selector: str,
|
|
245
|
+
*,
|
|
246
|
+
environment: Optional[str] = None,
|
|
247
|
+
target_app: Optional[str] = None,
|
|
248
|
+
architecture: Optional[str] = None,
|
|
249
|
+
runtime_family: Optional[str] = None,
|
|
250
|
+
actor_context: Optional[Dict[str, str]] = None,
|
|
251
|
+
) -> Dict[str, Any]:
|
|
252
|
+
qs = _query_string(
|
|
253
|
+
{
|
|
254
|
+
"environment": environment,
|
|
255
|
+
"target_app": target_app,
|
|
256
|
+
"architecture": architecture,
|
|
257
|
+
"runtime_family": runtime_family,
|
|
258
|
+
}
|
|
259
|
+
)
|
|
260
|
+
return await self.request(
|
|
261
|
+
f"/channels/{quote(artifact_key, safe='')}/{quote(selector, safe='')}{qs}",
|
|
262
|
+
method="GET",
|
|
263
|
+
headers=_actor_headers(actor_context),
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
async def pin_channel(
|
|
267
|
+
self,
|
|
268
|
+
artifact_key: str,
|
|
269
|
+
selector: str,
|
|
270
|
+
payload: Dict[str, Any],
|
|
271
|
+
*,
|
|
272
|
+
actor_context: Optional[Dict[str, str]] = None,
|
|
273
|
+
) -> Dict[str, Any]:
|
|
274
|
+
return await self.request(
|
|
275
|
+
f"/channels/{quote(artifact_key, safe='')}/{quote(selector, safe='')}/pin",
|
|
276
|
+
method="POST",
|
|
277
|
+
body=payload,
|
|
278
|
+
headers=_actor_headers(actor_context),
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
async def list_prestage(
|
|
282
|
+
self,
|
|
283
|
+
*,
|
|
284
|
+
actor_context: Optional[Dict[str, str]] = None,
|
|
285
|
+
) -> Dict[str, Any]:
|
|
286
|
+
return await self.request("/prestage", method="GET", headers=_actor_headers(actor_context))
|
|
287
|
+
|
|
288
|
+
async def ensure_prestage(
|
|
289
|
+
self,
|
|
290
|
+
payload: Dict[str, Any],
|
|
291
|
+
*,
|
|
292
|
+
actor_context: Optional[Dict[str, str]] = None,
|
|
293
|
+
) -> Dict[str, Any]:
|
|
294
|
+
return await self.request(
|
|
295
|
+
"/prestage/ensure",
|
|
296
|
+
method="POST",
|
|
297
|
+
body=payload,
|
|
298
|
+
headers=_actor_headers(actor_context),
|
|
299
|
+
timeout=600.0,
|
|
300
|
+
)
|
|
@@ -181,10 +181,12 @@ class Dominus:
|
|
|
181
181
|
from .namespaces.warden import WardenNamespace
|
|
182
182
|
from .namespaces.platform import PlatformNamespace
|
|
183
183
|
from .namespaces.coder import CoderNamespace
|
|
184
|
+
from .namespaces.publisher import PublisherNamespace
|
|
184
185
|
self.deployer = DeployerNamespace(self)
|
|
185
186
|
self.warden = WardenNamespace(self)
|
|
186
187
|
self.platform = PlatformNamespace(self)
|
|
187
188
|
self.coder = CoderNamespace(self)
|
|
189
|
+
self.publisher = PublisherNamespace(self)
|
|
188
190
|
|
|
189
191
|
# Stash worker (per-scope durable items: credentials + configs)
|
|
190
192
|
from .namespaces.stash import StashNamespace
|
|
@@ -539,6 +541,7 @@ class Dominus:
|
|
|
539
541
|
or "/svc/admin/" in path
|
|
540
542
|
or "/svc/platform/" in path
|
|
541
543
|
or "/svc/coder/" in path
|
|
544
|
+
or "/svc/publisher/" in path
|
|
542
545
|
)
|
|
543
546
|
decode_base64 = (
|
|
544
547
|
"/svc/warden/secrets" in path
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dominus-sdk-python
|
|
3
|
-
Version: 6.1.
|
|
3
|
+
Version: 6.1.2
|
|
4
4
|
Summary: Python SDK for the Dominus gateway-first platform
|
|
5
5
|
Author-email: CareBridge Systems <dev@carebridge.io>
|
|
6
6
|
License-Expression: LicenseRef-Proprietary
|
|
@@ -42,7 +42,7 @@ Async Python SDK for the Dominus gateway-first service plane.
|
|
|
42
42
|
- Gateway-scoped client mode for MCP and other user-JWT sessions
|
|
43
43
|
- Transport compatibility for wrapped `{success,data}` responses and unwrapped Warden/control-plane success objects
|
|
44
44
|
- Local helpers for JWT verification, trace propagation, retries, and console capture
|
|
45
|
-
- Current package version: `6.
|
|
45
|
+
- Current package version: `6.1.1`
|
|
46
46
|
|
|
47
47
|
## Install
|
|
48
48
|
|
|
@@ -94,6 +94,20 @@ machine_logs = await dominus.logs.tail(
|
|
|
94
94
|
since="2026-04-11T08:33:00Z",
|
|
95
95
|
level="error",
|
|
96
96
|
)
|
|
97
|
+
policy = await dominus.platform.ensure_policy_decision(
|
|
98
|
+
{
|
|
99
|
+
"group": "dominus",
|
|
100
|
+
"repository": "carebridgesystems/dominus-platform-worker",
|
|
101
|
+
},
|
|
102
|
+
actor_context={"type": "user", "id": "operator-1"},
|
|
103
|
+
)
|
|
104
|
+
coder_run = await dominus.coder.ensure_run(
|
|
105
|
+
policy_decision_id=policy["data"]["policy_decision"]["decision_id"],
|
|
106
|
+
workflow_recipe_ref="recipe://workflow-recipe-v1/coder-feature@head",
|
|
107
|
+
repository="carebridgesystems/dominus-platform-worker",
|
|
108
|
+
instructions="Fix failing tests",
|
|
109
|
+
actor_context={"type": "user", "id": "operator-1"},
|
|
110
|
+
)
|
|
97
111
|
|
|
98
112
|
# Archive maintenance is explicit and dry-run first.
|
|
99
113
|
verify = await dominus.authority.verify_timeline_archive_manifests(
|
|
@@ -206,6 +220,8 @@ JWT and selected scope headers directly through Gateway.
|
|
|
206
220
|
| `browser` | Browser Worker | Browser run health, ensure/start/status/result/retry/nudge/cancel/timeline/dossier |
|
|
207
221
|
| `deployer` | Deployer | Thin operator control-plane request surface |
|
|
208
222
|
| `warden` | Warden | Thin operator control-plane request surface |
|
|
223
|
+
| `platform` | Platform Worker | Group/repository policy decisions with actor attribution |
|
|
224
|
+
| `coder` | Coder Runtime | Policy-bound Coder run lifecycle with workflow/pipeline recipe launch sources |
|
|
209
225
|
| `fastapi` | Local decorators | `@jwt`, `@psk`, `@scopes(...)` |
|
|
210
226
|
|
|
211
227
|
## Root Shortcuts
|
{dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus_sdk_python.egg-info/SOURCES.txt
RENAMED
|
@@ -33,6 +33,7 @@ dominus/namespaces/logs.py
|
|
|
33
33
|
dominus/namespaces/platform.py
|
|
34
34
|
dominus/namespaces/portal.py
|
|
35
35
|
dominus/namespaces/processor.py
|
|
36
|
+
dominus/namespaces/publisher.py
|
|
36
37
|
dominus/namespaces/recipes.py
|
|
37
38
|
dominus/namespaces/redis.py
|
|
38
39
|
dominus/namespaces/secrets.py
|
|
@@ -58,6 +59,7 @@ tests/test_logs.py
|
|
|
58
59
|
tests/test_platform_coder_namespaces.py
|
|
59
60
|
tests/test_provisioning_parity.py
|
|
60
61
|
tests/test_public_exports.py
|
|
62
|
+
tests/test_publisher_namespace.py
|
|
61
63
|
tests/test_recipes_namespace.py
|
|
62
64
|
tests/test_transport_compat.py
|
|
63
65
|
tests/test_workflow_lifecycle.py
|
{dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/tests/test_platform_coder_namespaces.py
RENAMED
|
@@ -31,7 +31,8 @@ async def test_platform_namespace_normalizes_helpers_onto_service_routes():
|
|
|
31
31
|
"group": "dominus",
|
|
32
32
|
"repository": "carebridgesystems/dominus-platform-worker",
|
|
33
33
|
"metadata": {"safe": "ok"},
|
|
34
|
-
}
|
|
34
|
+
},
|
|
35
|
+
actor_context={"type": "user", "id": "user-1"},
|
|
35
36
|
)
|
|
36
37
|
await platform.rehydrate_policy_decision("pd_123", {"run_id": "run_123"})
|
|
37
38
|
|
|
@@ -51,6 +52,7 @@ async def test_platform_namespace_normalizes_helpers_onto_service_routes():
|
|
|
51
52
|
"repository": "carebridgesystems/dominus-platform-worker",
|
|
52
53
|
"metadata": {"safe": "ok"},
|
|
53
54
|
}
|
|
55
|
+
assert client.calls[5][1]["headers"] == {"X-Actor-Type": "user", "X-Actor-Id": "user-1"}
|
|
54
56
|
|
|
55
57
|
|
|
56
58
|
@pytest.mark.asyncio
|
|
@@ -76,8 +78,10 @@ async def test_coder_namespace_exposes_lifecycle_helpers_but_no_raw_shell():
|
|
|
76
78
|
policy_decision_id="pd_123",
|
|
77
79
|
mode="async",
|
|
78
80
|
task_recipe_ref="recipe://coder-task-recipe-v1/fix-tests@head",
|
|
81
|
+
workflow_recipe_ref="recipe://workflow-recipe-v1/coder-feature@head",
|
|
79
82
|
repository="carebridgesystems/dominus-platform-worker",
|
|
80
83
|
instructions="Fix failing tests",
|
|
84
|
+
actor_context={"type": "user", "id": "user-1"},
|
|
81
85
|
)
|
|
82
86
|
await coder.list_runs(status="running", limit=10)
|
|
83
87
|
await coder.cancel_run("run_123", reason="operator requested")
|
|
@@ -99,9 +103,11 @@ async def test_coder_namespace_exposes_lifecycle_helpers_but_no_raw_shell():
|
|
|
99
103
|
"policy_decision_id": "pd_123",
|
|
100
104
|
"mode": "async",
|
|
101
105
|
"task_recipe_ref": "recipe://coder-task-recipe-v1/fix-tests@head",
|
|
106
|
+
"workflow_recipe_ref": "recipe://workflow-recipe-v1/coder-feature@head",
|
|
102
107
|
"repository": "carebridgesystems/dominus-platform-worker",
|
|
103
108
|
"instructions": "Fix failing tests",
|
|
104
109
|
}
|
|
110
|
+
assert client.calls[0][1]["headers"] == {"X-Actor-Type": "user", "X-Actor-Id": "user-1"}
|
|
105
111
|
assert not hasattr(coder, "shell")
|
|
106
112
|
assert not hasattr(coder, "exec")
|
|
107
113
|
assert not hasattr(coder, "command")
|
|
@@ -116,3 +122,21 @@ async def test_coder_ensure_run_requires_policy_decision_id():
|
|
|
116
122
|
await coder.ensure_run(policy_decision_id="")
|
|
117
123
|
|
|
118
124
|
assert client.calls == []
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@pytest.mark.asyncio
|
|
128
|
+
async def test_coder_ensure_run_requires_exactly_one_authority_launch_source():
|
|
129
|
+
client = MockGatewayClient()
|
|
130
|
+
coder = CoderNamespace(client)
|
|
131
|
+
|
|
132
|
+
with pytest.raises(ValueError, match="exactly one"):
|
|
133
|
+
await coder.ensure_run(policy_decision_id="pd_123")
|
|
134
|
+
|
|
135
|
+
with pytest.raises(ValueError, match="exactly one"):
|
|
136
|
+
await coder.ensure_run(
|
|
137
|
+
policy_decision_id="pd_123",
|
|
138
|
+
workflow_recipe_ref="recipe://workflow-recipe-v1/coder@head",
|
|
139
|
+
pipeline_recipe_ref="recipe://pipeline-recipe-v1/coder@head",
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
assert client.calls == []
|
|
@@ -3,6 +3,7 @@ from dominus import (
|
|
|
3
3
|
CoderNamespace,
|
|
4
4
|
DeployerNamespace,
|
|
5
5
|
PlatformNamespace,
|
|
6
|
+
PublisherNamespace,
|
|
6
7
|
RecipesNamespace,
|
|
7
8
|
WardenNamespace,
|
|
8
9
|
gateway_circuit_breaker,
|
|
@@ -37,6 +38,7 @@ def test_top_level_exports_drop_delegate_alias():
|
|
|
37
38
|
assert RecipesNamespace is not None
|
|
38
39
|
assert PlatformNamespace is not None
|
|
39
40
|
assert CoderNamespace is not None
|
|
41
|
+
assert PublisherNamespace is not None
|
|
40
42
|
|
|
41
43
|
|
|
42
44
|
def test_singleton_exposes_browser_namespace():
|
|
@@ -46,3 +48,4 @@ def test_singleton_exposes_browser_namespace():
|
|
|
46
48
|
assert callable(dominus.recipes.list_types)
|
|
47
49
|
assert callable(dominus.platform.ensure_policy_decision)
|
|
48
50
|
assert callable(dominus.coder.ensure_run)
|
|
51
|
+
assert callable(dominus.publisher.resolve_artifact)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from dominus.namespaces.publisher import PublisherNamespace
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class MockGatewayClient:
|
|
7
|
+
def __init__(self):
|
|
8
|
+
self.calls = []
|
|
9
|
+
|
|
10
|
+
async def gateway_fetch(self, path, **kwargs):
|
|
11
|
+
self.calls.append((path, kwargs))
|
|
12
|
+
return {"ok": True}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest.mark.asyncio
|
|
16
|
+
async def test_publisher_namespace_normalizes_lifecycle_and_resolver_helpers():
|
|
17
|
+
client = MockGatewayClient()
|
|
18
|
+
publisher = PublisherNamespace(client)
|
|
19
|
+
|
|
20
|
+
await publisher.health()
|
|
21
|
+
await publisher.ready()
|
|
22
|
+
await publisher.list_builds()
|
|
23
|
+
await publisher.ensure_build(
|
|
24
|
+
{"artifact_key": "envoy_binary"},
|
|
25
|
+
actor_context={"type": "service", "id": "dominus-deployer"},
|
|
26
|
+
)
|
|
27
|
+
await publisher.get_build("pub_123")
|
|
28
|
+
await publisher.retry_build("pub_123", reason="rerun", idempotency_key="retry-1")
|
|
29
|
+
await publisher.cancel_build("pub_123", metadata={"requested_by": "operator"})
|
|
30
|
+
await publisher.evaluate_signing({"environment": "production"})
|
|
31
|
+
await publisher.record_signing_evidence({"artifact_key": "envoy_binary"})
|
|
32
|
+
await publisher.list_artifacts()
|
|
33
|
+
await publisher.record_release({"artifact_key": "envoy_binary"})
|
|
34
|
+
await publisher.resolve_artifact(
|
|
35
|
+
"envoy_binary",
|
|
36
|
+
selector="stable",
|
|
37
|
+
environment="production",
|
|
38
|
+
target_app="powerscribe",
|
|
39
|
+
architecture="x64",
|
|
40
|
+
runtime_family="rust-tauri",
|
|
41
|
+
)
|
|
42
|
+
await publisher.list_channels()
|
|
43
|
+
await publisher.get_channel("envoy_binary", "stable", environment="production")
|
|
44
|
+
await publisher.pin_channel("envoy_binary", "stable", {"release_ref": "ar://..."})
|
|
45
|
+
await publisher.list_prestage()
|
|
46
|
+
|
|
47
|
+
assert [(path, kwargs["method"]) for path, kwargs in client.calls] == [
|
|
48
|
+
("/svc/publisher/health", "GET"),
|
|
49
|
+
("/svc/publisher/ready", "GET"),
|
|
50
|
+
("/svc/publisher/builds", "GET"),
|
|
51
|
+
("/svc/publisher/builds/ensure", "POST"),
|
|
52
|
+
("/svc/publisher/builds/pub_123", "GET"),
|
|
53
|
+
("/svc/publisher/builds/pub_123/retry", "POST"),
|
|
54
|
+
("/svc/publisher/builds/pub_123/cancel", "POST"),
|
|
55
|
+
("/svc/publisher/builds/signing/evaluate", "POST"),
|
|
56
|
+
("/svc/publisher/builds/signing/evidence", "POST"),
|
|
57
|
+
("/svc/publisher/artifacts", "GET"),
|
|
58
|
+
("/svc/publisher/artifacts/releases/record", "POST"),
|
|
59
|
+
("/svc/publisher/artifacts/envoy_binary?selector=stable&environment=production&target_app=powerscribe&architecture=x64&runtime_family=rust-tauri", "GET"),
|
|
60
|
+
("/svc/publisher/channels", "GET"),
|
|
61
|
+
("/svc/publisher/channels/envoy_binary/stable?environment=production", "GET"),
|
|
62
|
+
("/svc/publisher/channels/envoy_binary/stable/pin", "POST"),
|
|
63
|
+
("/svc/publisher/prestage", "GET"),
|
|
64
|
+
]
|
|
65
|
+
assert client.calls[3][1]["headers"] == {"X-Actor-Type": "service", "X-Actor-Id": "dominus-deployer"}
|
|
66
|
+
assert client.calls[5][1]["body"] == {"reason": "rerun", "idempotency_key": "retry-1"}
|
|
67
|
+
assert not hasattr(publisher, "shell")
|
|
68
|
+
assert not hasattr(publisher, "command")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@pytest.mark.asyncio
|
|
72
|
+
async def test_publisher_request_rewrites_api_paths_to_svc_paths():
|
|
73
|
+
client = MockGatewayClient()
|
|
74
|
+
publisher = PublisherNamespace(client)
|
|
75
|
+
|
|
76
|
+
await publisher.request("/api/publisher/builds", method="GET")
|
|
77
|
+
await publisher.request("/svc/publisher/channels", method="GET")
|
|
78
|
+
|
|
79
|
+
assert [path for path, _ in client.calls] == [
|
|
80
|
+
"/svc/publisher/builds",
|
|
81
|
+
"/svc/publisher/channels",
|
|
82
|
+
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus_sdk_python.egg-info/requires.txt
RENAMED
|
File without changes
|
{dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/dominus_sdk_python.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/tests/test_authority_public_vocabulary.py
RENAMED
|
File without changes
|
|
File without changes
|
{dominus_sdk_python-6.1.0 → dominus_sdk_python-6.1.2}/tests/test_control_plane_namespaces.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|