dominus-sdk-python 6.1.3__tar.gz → 6.3.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 (70) hide show
  1. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/PKG-INFO +1 -1
  2. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus/__init__.py +15 -1
  3. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus/errors.py +41 -1
  4. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus/namespaces/authority.py +2 -0
  5. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus/namespaces/recipes.py +109 -3
  6. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus/namespaces/stash.py +160 -0
  7. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus_sdk_python.egg-info/PKG-INFO +1 -1
  8. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus_sdk_python.egg-info/SOURCES.txt +2 -0
  9. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/pyproject.toml +1 -1
  10. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/tests/test_authority_public_vocabulary.py +2 -1
  11. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/tests/test_recipes_namespace.py +2 -2
  12. dominus_sdk_python-6.3.0/tests/test_recipes_stash_routing.py +229 -0
  13. dominus_sdk_python-6.3.0/tests/test_stash_artifact_facade.py +355 -0
  14. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/README.md +0 -0
  15. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus/config/__init__.py +0 -0
  16. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus/config/endpoints.py +0 -0
  17. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus/helpers/__init__.py +0 -0
  18. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus/helpers/auth.py +0 -0
  19. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus/helpers/cache.py +0 -0
  20. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus/helpers/console_capture.py +0 -0
  21. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus/helpers/core.py +0 -0
  22. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus/helpers/crypto.py +0 -0
  23. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus/helpers/sse.py +0 -0
  24. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus/helpers/trace.py +0 -0
  25. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus/namespaces/__init__.py +0 -0
  26. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus/namespaces/admin.py +0 -0
  27. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus/namespaces/ai.py +0 -0
  28. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus/namespaces/artifacts.py +0 -0
  29. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus/namespaces/auth.py +0 -0
  30. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus/namespaces/browser.py +0 -0
  31. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus/namespaces/coder.py +0 -0
  32. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus/namespaces/courier.py +0 -0
  33. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus/namespaces/db.py +0 -0
  34. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus/namespaces/ddl.py +0 -0
  35. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus/namespaces/deployer.py +0 -0
  36. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus/namespaces/fastapi.py +0 -0
  37. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus/namespaces/files.py +0 -0
  38. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus/namespaces/health.py +0 -0
  39. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus/namespaces/jobs.py +0 -0
  40. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus/namespaces/logs.py +0 -0
  41. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus/namespaces/platform.py +0 -0
  42. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus/namespaces/portal.py +0 -0
  43. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus/namespaces/processor.py +0 -0
  44. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus/namespaces/publisher.py +0 -0
  45. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus/namespaces/redis.py +0 -0
  46. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus/namespaces/secrets.py +0 -0
  47. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus/namespaces/secure.py +0 -0
  48. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus/namespaces/sync.py +0 -0
  49. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus/namespaces/warden.py +0 -0
  50. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus/namespaces/workflow.py +0 -0
  51. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus/services/__init__.py +0 -0
  52. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus/start.py +0 -0
  53. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus_sdk_python.egg-info/dependency_links.txt +0 -0
  54. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus_sdk_python.egg-info/requires.txt +0 -0
  55. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/dominus_sdk_python.egg-info/top_level.txt +0 -0
  56. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/setup.cfg +0 -0
  57. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/tests/test_auth.py +0 -0
  58. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/tests/test_browser_namespace.py +0 -0
  59. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/tests/test_control_plane_namespaces.py +0 -0
  60. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/tests/test_errors.py +0 -0
  61. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/tests/test_flat_commands.py +0 -0
  62. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/tests/test_health.py +0 -0
  63. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/tests/test_logs.py +0 -0
  64. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/tests/test_platform_coder_namespaces.py +0 -0
  65. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/tests/test_provisioning_parity.py +0 -0
  66. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/tests/test_public_exports.py +0 -0
  67. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/tests/test_publisher_namespace.py +0 -0
  68. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/tests/test_transport_compat.py +0 -0
  69. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/tests/test_workflow_lifecycle.py +0 -0
  70. {dominus_sdk_python-6.1.3 → dominus_sdk_python-6.3.0}/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
3
+ Version: 6.3.0
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
@@ -160,12 +160,19 @@ from .errors import (
160
160
  ValidationError,
161
161
  ConflictError,
162
162
  ServiceError,
163
+ StashBookmarkArtifactNotFound,
164
+ StashBookmarkInvalidVersionRef,
165
+ StashBookmarkDuplicateName,
166
+ StashBookmarkUpstreamRejected,
167
+ StashWatchInvalidWebhookUrl,
168
+ StashWatchUpstreamRejected,
169
+ StashWatchNotFound,
163
170
  SecureTableError,
164
171
  ConnectionError as DominusConnectionError,
165
172
  TimeoutError as DominusTimeoutError,
166
173
  )
167
174
 
168
- __version__ = "6.1.3"
175
+ __version__ = "6.3.0"
169
176
  __all__ = [
170
177
  # Main SDK instance
171
178
  "dominus",
@@ -250,6 +257,13 @@ __all__ = [
250
257
  "ValidationError",
251
258
  "ConflictError",
252
259
  "ServiceError",
260
+ "StashBookmarkArtifactNotFound",
261
+ "StashBookmarkInvalidVersionRef",
262
+ "StashBookmarkDuplicateName",
263
+ "StashBookmarkUpstreamRejected",
264
+ "StashWatchInvalidWebhookUrl",
265
+ "StashWatchUpstreamRejected",
266
+ "StashWatchNotFound",
253
267
  "SecureTableError",
254
268
  "DominusConnectionError",
255
269
  "DominusTimeoutError",
@@ -205,6 +205,34 @@ class ServiceError(DominusError):
205
205
  super().__init__(message, status_code, details, endpoint)
206
206
 
207
207
 
208
+ class StashBookmarkArtifactNotFound(NotFoundError):
209
+ """Raised when a stash artifact bookmark target cannot be found."""
210
+
211
+
212
+ class StashBookmarkInvalidVersionRef(ValidationError):
213
+ """Raised when a stash artifact bookmark version_ref is invalid."""
214
+
215
+
216
+ class StashBookmarkDuplicateName(ConflictError):
217
+ """Raised when a stash artifact bookmark name is already in use."""
218
+
219
+
220
+ class StashBookmarkUpstreamRejected(ServiceError):
221
+ """Raised when the artifact backend rejects a stash bookmark operation."""
222
+
223
+
224
+ class StashWatchInvalidWebhookUrl(ValidationError):
225
+ """Raised when a stash artifact watcher webhook_url is invalid."""
226
+
227
+
228
+ class StashWatchUpstreamRejected(ServiceError):
229
+ """Raised when the artifact backend rejects a stash watch operation."""
230
+
231
+
232
+ class StashWatchNotFound(NotFoundError):
233
+ """Raised when a stash artifact watcher cannot be found."""
234
+
235
+
208
236
  class ConnectionError(DominusError):
209
237
  """Raised when connection to the backend fails."""
210
238
 
@@ -244,6 +272,17 @@ class SecureTableError(DominusError):
244
272
  super().__init__(message, status_code, details, endpoint)
245
273
 
246
274
 
275
+ STASH_ERROR_CLASSES = {
276
+ "stash.bookmark.artifact_not_found": StashBookmarkArtifactNotFound,
277
+ "stash.bookmark.invalid_version_ref": StashBookmarkInvalidVersionRef,
278
+ "stash.bookmark.duplicate_name": StashBookmarkDuplicateName,
279
+ "stash.bookmark.upstream_rejected": StashBookmarkUpstreamRejected,
280
+ "stash.watch.invalid_webhook_url": StashWatchInvalidWebhookUrl,
281
+ "stash.watch.upstream_rejected": StashWatchUpstreamRejected,
282
+ "stash.watch.not_found": StashWatchNotFound,
283
+ }
284
+
285
+
247
286
  def raise_for_status(
248
287
  status_code: int,
249
288
  message: str,
@@ -262,6 +301,7 @@ def raise_for_status(
262
301
  Raises:
263
302
  Appropriate DominusError subclass
264
303
  """
304
+ code = _first_string((details or {}).get("code"))
265
305
  error_classes = {
266
306
  400: ValidationError,
267
307
  401: AuthenticationError,
@@ -274,5 +314,5 @@ def raise_for_status(
274
314
  504: TimeoutError,
275
315
  }
276
316
 
277
- error_class = error_classes.get(status_code, DominusError)
317
+ error_class = STASH_ERROR_CLASSES.get(code) or error_classes.get(status_code, DominusError)
278
318
  raise error_class(message, status_code, details, endpoint)
@@ -1155,6 +1155,7 @@ class AuthorityNamespace:
1155
1155
  until: Optional[str] = None,
1156
1156
  window_hours: Optional[int] = None,
1157
1157
  limit: Optional[int] = None,
1158
+ full: bool = False,
1158
1159
  timeout: Optional[float] = None,
1159
1160
  ) -> Dict[str, Any]:
1160
1161
  """
@@ -1172,6 +1173,7 @@ class AuthorityNamespace:
1172
1173
  "until": until,
1173
1174
  "window_hours": window_hours,
1174
1175
  "limit": limit,
1176
+ "full": 1 if full else None,
1175
1177
  })
1176
1178
  return await self._get(
1177
1179
  f"/api/authority/dossiers/deploy/{quote(deploy_id, safe='')}{qs}",
@@ -12,16 +12,76 @@ Refs follow ``recipe://{type}/{name}[@v{N}][?tier={tier}]``.
12
12
  """
13
13
  from __future__ import annotations
14
14
 
15
+ import json
15
16
  from typing import Any, Dict, List, Optional, TYPE_CHECKING
16
17
 
17
18
  if TYPE_CHECKING:
18
19
  from ..start import Dominus
19
20
 
20
21
 
22
+ STASH_BACKED_RECIPE_TYPES = frozenset({
23
+ "envoy-bootstrap-v1",
24
+ "envoy-release-publish-v1",
25
+ "envoy-policy-v1",
26
+ "pacs-extraction-recipe-v1",
27
+ "pacs-vendor-probe-v1",
28
+ "browser-recipe",
29
+ })
30
+
31
+
21
32
  def _compact(payload: Dict[str, Any]) -> Dict[str, Any]:
22
33
  return {k: v for k, v in payload.items() if v is not None and v != ""}
23
34
 
24
35
 
36
+ def _recipe_tier_to_stash_scope(tier: Optional[str]) -> str:
37
+ if tier == "platform":
38
+ return "platform"
39
+ if tier == "group":
40
+ return "group"
41
+ return "self"
42
+
43
+
44
+ def _resolve_recipe_stash_env(client: "Dominus", env: Optional[str]) -> str:
45
+ return env or getattr(client, "_gateway_env", None) or "production"
46
+
47
+
48
+ def _recipe_body_to_text(value: Any) -> str:
49
+ if isinstance(value, str):
50
+ return value
51
+ if value is None:
52
+ raise ValueError("body is required")
53
+ return json.dumps(value, separators=(",", ":"))
54
+
55
+
56
+ def _build_recipe_payload_from_stash(
57
+ *,
58
+ type: str,
59
+ name: str,
60
+ tier: Optional[str],
61
+ data: Dict[str, Any],
62
+ ) -> Dict[str, Any]:
63
+ version = data.get("version")
64
+ try:
65
+ version_int = int(version) if version is not None else None
66
+ except (TypeError, ValueError):
67
+ version_int = None
68
+ return {
69
+ "recipe": {
70
+ "metadata": {
71
+ "type": type,
72
+ "name": name,
73
+ "tier": data.get("resolved_tier") or tier,
74
+ "version": version_int,
75
+ "schema_version": 1,
76
+ "sha256": data.get("value_hash"),
77
+ "head_ref": data.get("head_ref"),
78
+ "snapshot_ref": data.get("snapshot_ref"),
79
+ },
80
+ "body": data.get("value"),
81
+ },
82
+ }
83
+
84
+
25
85
  class RecipesNamespace:
26
86
  """
27
87
  Recipe worker namespace.
@@ -86,16 +146,41 @@ class RecipesNamespace:
86
146
  body: str,
87
147
  description: Optional[str] = None,
88
148
  actor_context: Optional[Dict[str, str]] = None,
149
+ env: Optional[str] = None,
89
150
  timeout: float = 30.0,
90
151
  ) -> Dict[str, Any]:
91
- """Publish a recipe. ``POST /api/recipe/recipes/publish``."""
152
+ """Publish a recipe. Stash-backed recipe types accept optional ``env``."""
153
+ body_text = _recipe_body_to_text(body)
154
+ if type in STASH_BACKED_RECIPE_TYPES:
155
+ result = await self._client._request(
156
+ endpoint="/svc/stash/put",
157
+ method="POST",
158
+ body={
159
+ "env": _resolve_recipe_stash_env(self._client, env),
160
+ "kind": type,
161
+ "scope": _recipe_tier_to_stash_scope(tier),
162
+ "key": name,
163
+ "value": body_text,
164
+ "versioned": True,
165
+ "purpose": description or "sdk-recipe-publish",
166
+ },
167
+ use_gateway=True,
168
+ actor=actor_context,
169
+ timeout=timeout,
170
+ )
171
+ return _build_recipe_payload_from_stash(
172
+ type=type,
173
+ name=name,
174
+ tier=tier,
175
+ data=result,
176
+ )
92
177
  return await self._post(
93
178
  "/api/recipe/recipes/publish",
94
179
  _compact({
95
180
  "type": type,
96
181
  "tier": tier,
97
182
  "name": name,
98
- "body": body,
183
+ "body": body_text,
99
184
  "description": description,
100
185
  }),
101
186
  actor_context=actor_context,
@@ -167,9 +252,30 @@ class RecipesNamespace:
167
252
  name: str,
168
253
  version: Optional[int] = None,
169
254
  tier: Optional[str] = None,
255
+ env: Optional[str] = None,
170
256
  timeout: float = 15.0,
171
257
  ) -> Dict[str, Any]:
172
- """Resolve a recipe through the three-tier chain. ``GET /api/recipe/recipes/{type}/{name}[@v{N}]``."""
258
+ """Resolve a recipe. Stash-backed recipe types accept optional ``env``."""
259
+ if type in STASH_BACKED_RECIPE_TYPES:
260
+ result = await self._client._request(
261
+ endpoint="/svc/stash/get",
262
+ method="POST",
263
+ body={
264
+ "env": _resolve_recipe_stash_env(self._client, env),
265
+ "kind": type,
266
+ "scope": _recipe_tier_to_stash_scope(tier),
267
+ "key": name,
268
+ "version": version if version is not None else "head",
269
+ },
270
+ use_gateway=True,
271
+ timeout=timeout,
272
+ )
273
+ return _build_recipe_payload_from_stash(
274
+ type=type,
275
+ name=name,
276
+ tier=tier,
277
+ data=result,
278
+ )
173
279
  path = f"/api/recipe/recipes/{type}/{name}"
174
280
  if version is not None:
175
281
  path += f"@v{version}"
@@ -6,10 +6,18 @@ stores items in each project's ``stash.*`` schema and transparently falls
6
6
  back to a designated shared project on read.
7
7
  """
8
8
  from typing import Any, Dict, List, Optional, TYPE_CHECKING
9
+ from urllib.parse import urlencode
9
10
 
10
11
  if TYPE_CHECKING:
11
12
  from ..start import Dominus
12
13
 
14
+ BookmarkRef = Dict[str, Any]
15
+ WatcherRef = Dict[str, Any]
16
+
17
+
18
+ def _facade_query(*, env: str, kind: str, scope: str, key: str) -> str:
19
+ return urlencode({"env": env, "kind": kind, "scope": scope, "key": key})
20
+
13
21
 
14
22
  class StashNamespace:
15
23
  """
@@ -273,3 +281,155 @@ class StashNamespace:
273
281
  body=body,
274
282
  use_gateway=True,
275
283
  )
284
+
285
+ async def bookmark(
286
+ self,
287
+ *,
288
+ env: str,
289
+ kind: str,
290
+ scope: str,
291
+ key: str,
292
+ name: str,
293
+ version_ref: str,
294
+ timeout: float = 30.0,
295
+ ) -> BookmarkRef:
296
+ """Create or update a named bookmark for a stash artifact facade item."""
297
+ return await self._client._request(
298
+ endpoint="/svc/stash/bookmark",
299
+ method="POST",
300
+ body={
301
+ "env": env,
302
+ "kind": kind,
303
+ "scope": scope,
304
+ "key": key,
305
+ "name": name,
306
+ "version_ref": version_ref,
307
+ },
308
+ use_gateway=True,
309
+ timeout=timeout,
310
+ )
311
+
312
+ async def unbookmark(
313
+ self,
314
+ *,
315
+ env: str,
316
+ kind: str,
317
+ scope: str,
318
+ key: str,
319
+ name: str,
320
+ timeout: float = 30.0,
321
+ ) -> None:
322
+ """Remove a named bookmark from a stash artifact facade item."""
323
+ await self._client._request(
324
+ endpoint="/svc/stash/unbookmark",
325
+ method="POST",
326
+ body={
327
+ "env": env,
328
+ "kind": kind,
329
+ "scope": scope,
330
+ "key": key,
331
+ "name": name,
332
+ },
333
+ use_gateway=True,
334
+ timeout=timeout,
335
+ )
336
+ return None
337
+
338
+ async def watch(
339
+ self,
340
+ *,
341
+ env: str,
342
+ kind: str,
343
+ scope: str,
344
+ key: str,
345
+ watcher_name: str,
346
+ webhook_url: str,
347
+ timeout: float = 30.0,
348
+ ) -> WatcherRef:
349
+ """Create or update a webhook watcher for a stash artifact facade item."""
350
+ return await self._client._request(
351
+ endpoint="/svc/stash/watch",
352
+ method="POST",
353
+ body={
354
+ "env": env,
355
+ "kind": kind,
356
+ "scope": scope,
357
+ "key": key,
358
+ "watcher_name": watcher_name,
359
+ "webhook_url": webhook_url,
360
+ },
361
+ use_gateway=True,
362
+ timeout=timeout,
363
+ )
364
+
365
+ async def unwatch(
366
+ self,
367
+ *,
368
+ env: str,
369
+ kind: str,
370
+ scope: str,
371
+ key: str,
372
+ watcher_name: str,
373
+ timeout: float = 30.0,
374
+ ) -> None:
375
+ """Remove a webhook watcher from a stash artifact facade item."""
376
+ await self._client._request(
377
+ endpoint="/svc/stash/unwatch",
378
+ method="POST",
379
+ body={
380
+ "env": env,
381
+ "kind": kind,
382
+ "scope": scope,
383
+ "key": key,
384
+ "watcher_name": watcher_name,
385
+ },
386
+ use_gateway=True,
387
+ timeout=timeout,
388
+ )
389
+ return None
390
+
391
+ async def list_bookmarks(
392
+ self,
393
+ *,
394
+ env: str,
395
+ kind: str,
396
+ scope: str,
397
+ key: str,
398
+ timeout: float = 30.0,
399
+ ) -> List[BookmarkRef]:
400
+ """List bookmark refs for a stash artifact facade item."""
401
+ result = await self._client._request(
402
+ endpoint=f"/svc/stash/bookmark/list?{_facade_query(env=env, kind=kind, scope=scope, key=key)}",
403
+ method="GET",
404
+ use_gateway=True,
405
+ timeout=timeout,
406
+ )
407
+ if isinstance(result, list):
408
+ return result
409
+ if isinstance(result, dict):
410
+ bookmarks = result.get("bookmarks")
411
+ return bookmarks if isinstance(bookmarks, list) else []
412
+ return []
413
+
414
+ async def list_watchers(
415
+ self,
416
+ *,
417
+ env: str,
418
+ kind: str,
419
+ scope: str,
420
+ key: str,
421
+ timeout: float = 30.0,
422
+ ) -> List[WatcherRef]:
423
+ """List watcher refs for a stash artifact facade item."""
424
+ result = await self._client._request(
425
+ endpoint=f"/svc/stash/watch/list?{_facade_query(env=env, kind=kind, scope=scope, key=key)}",
426
+ method="GET",
427
+ use_gateway=True,
428
+ timeout=timeout,
429
+ )
430
+ if isinstance(result, list):
431
+ return result
432
+ if isinstance(result, dict):
433
+ watchers = result.get("watchers")
434
+ return watchers if isinstance(watchers, list) else []
435
+ return []
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dominus-sdk-python
3
- Version: 6.1.3
3
+ Version: 6.3.0
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
@@ -61,6 +61,8 @@ tests/test_provisioning_parity.py
61
61
  tests/test_public_exports.py
62
62
  tests/test_publisher_namespace.py
63
63
  tests/test_recipes_namespace.py
64
+ tests/test_recipes_stash_routing.py
65
+ tests/test_stash_artifact_facade.py
64
66
  tests/test_transport_compat.py
65
67
  tests/test_workflow_lifecycle.py
66
68
  tests/test_workflow_refs.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.3"
7
+ version = "6.3.0"
8
8
  description = "Python SDK for the Dominus gateway-first platform"
9
9
  readme = "README.md"
10
10
  license = "LicenseRef-Proprietary"
@@ -217,6 +217,7 @@ async def test_authority_scope_methods_use_canonical_context_wire_names():
217
217
  until="2026-04-11T09:33:00Z",
218
218
  window_hours=24,
219
219
  limit=150,
220
+ full=True,
220
221
  )
221
222
  await namespace.query_timelines(
222
223
  subject="PCM47474562",
@@ -314,7 +315,7 @@ async def test_authority_scope_methods_use_canonical_context_wire_names():
314
315
  )
315
316
  assert client.calls[7]["endpoint"] == (
316
317
  "/api/authority/dossiers/deploy/deploy-1?"
317
- "since=2026-04-11T08%3A33%3A00Z&until=2026-04-11T09%3A33%3A00Z&window_hours=24&limit=150"
318
+ "since=2026-04-11T08%3A33%3A00Z&until=2026-04-11T09%3A33%3A00Z&window_hours=24&limit=150&full=1"
318
319
  )
319
320
  assert client.calls[8]["body"] == {
320
321
  "subject": "PCM47474562",
@@ -61,7 +61,7 @@ async def test_recipes_namespace_passes_actor_context_on_publish(monkeypatch, sd
61
61
 
62
62
  actor = {"type": "user", "id": "user-1"}
63
63
  await sdk.recipes.publish(
64
- type="browser-recipe",
64
+ type="legacy-recipe-type",
65
65
  tier="project",
66
66
  name="cms-login",
67
67
  body="version: browser-recipe-v1\nsteps: []\n",
@@ -73,7 +73,7 @@ async def test_recipes_namespace_passes_actor_context_on_publish(monkeypatch, sd
73
73
  "endpoint": "/api/recipe/recipes/publish",
74
74
  "method": "POST",
75
75
  "body": {
76
- "type": "browser-recipe",
76
+ "type": "legacy-recipe-type",
77
77
  "tier": "project",
78
78
  "name": "cms-login",
79
79
  "body": "version: browser-recipe-v1\nsteps: []\n",