dominus-sdk-python 6.1.1__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.
Files changed (68) hide show
  1. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/PKG-INFO +1 -1
  2. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/dominus/__init__.py +3 -1
  3. dominus_sdk_python-6.1.2/dominus/namespaces/publisher.py +300 -0
  4. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/dominus/start.py +3 -0
  5. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/dominus_sdk_python.egg-info/PKG-INFO +1 -1
  6. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/dominus_sdk_python.egg-info/SOURCES.txt +2 -0
  7. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/pyproject.toml +1 -1
  8. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/tests/test_public_exports.py +3 -0
  9. dominus_sdk_python-6.1.2/tests/test_publisher_namespace.py +82 -0
  10. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/README.md +0 -0
  11. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/dominus/config/__init__.py +0 -0
  12. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/dominus/config/endpoints.py +0 -0
  13. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/dominus/errors.py +0 -0
  14. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/dominus/helpers/__init__.py +0 -0
  15. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/dominus/helpers/auth.py +0 -0
  16. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/dominus/helpers/cache.py +0 -0
  17. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/dominus/helpers/console_capture.py +0 -0
  18. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/dominus/helpers/core.py +0 -0
  19. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/dominus/helpers/crypto.py +0 -0
  20. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/dominus/helpers/sse.py +0 -0
  21. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/dominus/helpers/trace.py +0 -0
  22. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/dominus/namespaces/__init__.py +0 -0
  23. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/dominus/namespaces/admin.py +0 -0
  24. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/dominus/namespaces/ai.py +0 -0
  25. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/dominus/namespaces/artifacts.py +0 -0
  26. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/dominus/namespaces/auth.py +0 -0
  27. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/dominus/namespaces/authority.py +0 -0
  28. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/dominus/namespaces/browser.py +0 -0
  29. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/dominus/namespaces/coder.py +0 -0
  30. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/dominus/namespaces/courier.py +0 -0
  31. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/dominus/namespaces/db.py +0 -0
  32. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/dominus/namespaces/ddl.py +0 -0
  33. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/dominus/namespaces/deployer.py +0 -0
  34. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/dominus/namespaces/fastapi.py +0 -0
  35. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/dominus/namespaces/files.py +0 -0
  36. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/dominus/namespaces/health.py +0 -0
  37. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/dominus/namespaces/jobs.py +0 -0
  38. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/dominus/namespaces/logs.py +0 -0
  39. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/dominus/namespaces/platform.py +0 -0
  40. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/dominus/namespaces/portal.py +0 -0
  41. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/dominus/namespaces/processor.py +0 -0
  42. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/dominus/namespaces/recipes.py +0 -0
  43. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/dominus/namespaces/redis.py +0 -0
  44. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/dominus/namespaces/secrets.py +0 -0
  45. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/dominus/namespaces/secure.py +0 -0
  46. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/dominus/namespaces/stash.py +0 -0
  47. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/dominus/namespaces/sync.py +0 -0
  48. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/dominus/namespaces/warden.py +0 -0
  49. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/dominus/namespaces/workflow.py +0 -0
  50. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/dominus/services/__init__.py +0 -0
  51. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/dominus_sdk_python.egg-info/dependency_links.txt +0 -0
  52. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/dominus_sdk_python.egg-info/requires.txt +0 -0
  53. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/dominus_sdk_python.egg-info/top_level.txt +0 -0
  54. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/setup.cfg +0 -0
  55. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/tests/test_auth.py +0 -0
  56. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/tests/test_authority_public_vocabulary.py +0 -0
  57. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/tests/test_browser_namespace.py +0 -0
  58. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/tests/test_control_plane_namespaces.py +0 -0
  59. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/tests/test_errors.py +0 -0
  60. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/tests/test_flat_commands.py +0 -0
  61. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/tests/test_health.py +0 -0
  62. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/tests/test_logs.py +0 -0
  63. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/tests/test_platform_coder_namespaces.py +0 -0
  64. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/tests/test_provisioning_parity.py +0 -0
  65. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/tests/test_recipes_namespace.py +0 -0
  66. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/tests/test_transport_compat.py +0 -0
  67. {dominus_sdk_python-6.1.1 → dominus_sdk_python-6.1.2}/tests/test_workflow_lifecycle.py +0 -0
  68. {dominus_sdk_python-6.1.1 → 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.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
@@ -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.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",
@@ -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.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
@@ -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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "dominus-sdk-python"
7
- version = "6.1.1"
7
+ version = "6.1.2"
8
8
  description = "Python SDK for the Dominus gateway-first platform"
9
9
  readme = "README.md"
10
10
  license = "LicenseRef-Proprietary"
@@ -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
+ ]