dominus-sdk-python 2.17.0__tar.gz → 2.18.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 (52) hide show
  1. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/PKG-INFO +11 -3
  2. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/README.md +10 -2
  3. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/dominus/__init__.py +1 -1
  4. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/workflow.py +256 -6
  5. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/dominus_sdk_python.egg-info/PKG-INFO +11 -3
  6. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/pyproject.toml +1 -1
  7. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/tests/test_workflow_lifecycle.py +74 -0
  8. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/dominus/config/__init__.py +0 -0
  9. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/dominus/config/endpoints.py +0 -0
  10. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/dominus/errors.py +0 -0
  11. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/dominus/helpers/__init__.py +0 -0
  12. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/dominus/helpers/auth.py +0 -0
  13. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/dominus/helpers/cache.py +0 -0
  14. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/dominus/helpers/console_capture.py +0 -0
  15. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/dominus/helpers/core.py +0 -0
  16. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/dominus/helpers/crypto.py +0 -0
  17. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/dominus/helpers/sse.py +0 -0
  18. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/dominus/helpers/trace.py +0 -0
  19. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/__init__.py +0 -0
  20. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/admin.py +0 -0
  21. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/ai.py +0 -0
  22. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/artifacts.py +0 -0
  23. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/auth.py +0 -0
  24. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/courier.py +0 -0
  25. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/db.py +0 -0
  26. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/ddl.py +0 -0
  27. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/fastapi.py +0 -0
  28. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/files.py +0 -0
  29. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/health.py +0 -0
  30. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/jobs.py +0 -0
  31. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/logs.py +0 -0
  32. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/open.py +0 -0
  33. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/oracle/__init__.py +0 -0
  34. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/oracle/audio_capture.py +0 -0
  35. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/oracle/oracle_websocket.py +0 -0
  36. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/oracle/session.py +0 -0
  37. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/oracle/types.py +0 -0
  38. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/oracle/vad_gate.py +0 -0
  39. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/portal.py +0 -0
  40. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/processor.py +0 -0
  41. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/redis.py +0 -0
  42. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/secrets.py +0 -0
  43. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/secure.py +0 -0
  44. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/dominus/namespaces/sync.py +0 -0
  45. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/dominus/services/__init__.py +0 -0
  46. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/dominus/start.py +0 -0
  47. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/dominus_sdk_python.egg-info/SOURCES.txt +0 -0
  48. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/dominus_sdk_python.egg-info/dependency_links.txt +0 -0
  49. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/dominus_sdk_python.egg-info/requires.txt +0 -0
  50. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/dominus_sdk_python.egg-info/top_level.txt +0 -0
  51. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.0}/setup.cfg +0 -0
  52. {dominus_sdk_python-2.17.0 → dominus_sdk_python-2.18.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: 2.17.0
3
+ Version: 2.18.0
4
4
  Summary: Python SDK for the Dominus Orchestrator Platform
5
5
  Author-email: CareBridge Systems <dev@carebridge.io>
6
6
  License: Proprietary
@@ -49,7 +49,7 @@ Async Python SDK for CareBridge Dominus platform services. Routes calls through
49
49
  - Server-side, asyncio-first Python SDK (3.9+)
50
50
  - Namespace API with root-level shortcuts for common operations
51
51
  - Targets production Cloudflare Workers (gateway, JWT, logs) and Cloud Run (orchestrator)
52
- - Version: 2.16.0
52
+ - Version: 2.18.0
53
53
 
54
54
  ## Quick Start
55
55
 
@@ -89,7 +89,15 @@ async for chunk in dominus.ai.stream_agent(
89
89
  ):
90
90
  print(chunk.get("content", ""), end="", flush=True)
91
91
 
92
- # Saved workflow lifecycle through workflow-manager
92
+ # Preferred Authority-backed one-call lifecycle
93
+ execution = await dominus.workflow.ensure(
94
+ "wf://carebridge/report-cycle",
95
+ subject="PCM47474562",
96
+ company="summit-radiology",
97
+ inputs={"report_snapshot": "ar://carebridge/summit-radiology/production/snapshot/report-1"},
98
+ )
99
+
100
+ # Saved workflow lifecycle through workflow-manager when you need explicit artifacts
93
101
  run = await dominus.workflow.create_run(
94
102
  workflow_id="wf_saved_123",
95
103
  context={"report_ref": {"report_id": "r-1", "accession": "a-1", "session_number": "2"}},
@@ -7,7 +7,7 @@ Async Python SDK for CareBridge Dominus platform services. Routes calls through
7
7
  - Server-side, asyncio-first Python SDK (3.9+)
8
8
  - Namespace API with root-level shortcuts for common operations
9
9
  - Targets production Cloudflare Workers (gateway, JWT, logs) and Cloud Run (orchestrator)
10
- - Version: 2.16.0
10
+ - Version: 2.18.0
11
11
 
12
12
  ## Quick Start
13
13
 
@@ -47,7 +47,15 @@ async for chunk in dominus.ai.stream_agent(
47
47
  ):
48
48
  print(chunk.get("content", ""), end="", flush=True)
49
49
 
50
- # Saved workflow lifecycle through workflow-manager
50
+ # Preferred Authority-backed one-call lifecycle
51
+ execution = await dominus.workflow.ensure(
52
+ "wf://carebridge/report-cycle",
53
+ subject="PCM47474562",
54
+ company="summit-radiology",
55
+ inputs={"report_snapshot": "ar://carebridge/summit-radiology/production/snapshot/report-1"},
56
+ )
57
+
58
+ # Saved workflow lifecycle through workflow-manager when you need explicit artifacts
51
59
  run = await dominus.workflow.create_run(
52
60
  workflow_id="wf_saved_123",
53
61
  context={"report_ref": {"report_id": "r-1", "accession": "a-1", "session_number": "2"}},
@@ -180,7 +180,7 @@ from .errors import (
180
180
  TimeoutError as DominusTimeoutError,
181
181
  )
182
182
 
183
- __version__ = "2.17.0"
183
+ __version__ = "2.18.0"
184
184
  __all__ = [
185
185
  # Main SDK instance
186
186
  "dominus",
@@ -34,6 +34,9 @@ Usage:
34
34
  await dominus.workflow.validate_run(run["run_id"])
35
35
  result = await dominus.workflow.start_run(run["run_id"], mode="async")
36
36
  """
37
+ import base64
38
+ import json
39
+ import uuid
37
40
  from typing import Any, Dict, List, Optional, TYPE_CHECKING
38
41
  from urllib.parse import urlencode
39
42
 
@@ -154,6 +157,90 @@ class WorkflowNamespace:
154
157
  body["context"] = context
155
158
  return body
156
159
 
160
+ @staticmethod
161
+ def _decode_authority_run_id(run_id: str) -> Optional[Dict[str, str]]:
162
+ normalized = str(run_id or "").strip()
163
+ if not normalized:
164
+ return None
165
+ try:
166
+ padding = "=" * ((4 - len(normalized) % 4) % 4)
167
+ decoded = base64.urlsafe_b64decode((normalized + padding).encode("utf-8")).decode("utf-8")
168
+ payload = json.loads(decoded)
169
+ except Exception:
170
+ return None
171
+
172
+ required = ("project_id", "environment", "workflow_id", "instance_id")
173
+ if all(isinstance(payload.get(field), str) and str(payload.get(field)).strip() for field in required):
174
+ return {field: str(payload[field]).strip() for field in required}
175
+ return None
176
+
177
+ def _is_authority_run_id(self, run_id: str) -> bool:
178
+ return self._decode_authority_run_id(run_id) is not None
179
+
180
+ @staticmethod
181
+ def _authority_mutation_body(
182
+ body: Dict[str, Any],
183
+ *,
184
+ idempotency_key: Optional[str] = None,
185
+ initiator_type: Optional[str] = None,
186
+ initiator_id: Optional[str] = None,
187
+ ) -> Dict[str, Any]:
188
+ result = dict(body)
189
+ if initiator_type:
190
+ result["initiator_type"] = initiator_type
191
+ if initiator_id:
192
+ result["initiator_id"] = initiator_id
193
+ result["idempotency_key"] = idempotency_key or uuid.uuid4().hex
194
+ return result
195
+
196
+ def _build_authority_ensure_body(
197
+ self,
198
+ workflow_id: str,
199
+ *,
200
+ workflow_ref: Optional[str] = None,
201
+ subject: Optional[str] = None,
202
+ company: Optional[str] = None,
203
+ inputs: Optional[Dict[str, Any]] = None,
204
+ context: Optional[Dict[str, Any]] = None,
205
+ bindings: Optional[Dict[str, Any]] = None,
206
+ instance_id: Optional[str] = None,
207
+ mode: str = "async",
208
+ target_project_id: Optional[str] = None,
209
+ target_environment: Optional[str] = None,
210
+ initiator_type: Optional[str] = None,
211
+ initiator_id: Optional[str] = None,
212
+ idempotency_key: Optional[str] = None,
213
+ ) -> Dict[str, Any]:
214
+ body: Dict[str, Any] = {"mode": mode}
215
+ if workflow_ref:
216
+ body["workflow_ref"] = workflow_ref
217
+ elif self._is_workflow_ref(workflow_id):
218
+ body["workflow_ref"] = workflow_id
219
+ else:
220
+ body["workflow_id"] = workflow_id
221
+ if subject is not None:
222
+ body["subject"] = subject
223
+ if company is not None:
224
+ body["company"] = company
225
+ if inputs:
226
+ body["inputs"] = inputs
227
+ if context:
228
+ body["context"] = context
229
+ if bindings:
230
+ body["bindings"] = bindings
231
+ if instance_id:
232
+ body["instance_id"] = instance_id
233
+ if target_project_id:
234
+ body["target_project_id"] = target_project_id
235
+ if target_environment:
236
+ body["target_environment"] = target_environment
237
+ return self._authority_mutation_body(
238
+ body,
239
+ idempotency_key=idempotency_key,
240
+ initiator_type=initiator_type,
241
+ initiator_id=initiator_id,
242
+ )
243
+
157
244
  async def _api(
158
245
  self,
159
246
  endpoint: str,
@@ -240,6 +327,16 @@ class WorkflowNamespace:
240
327
  Returns:
241
328
  Dict with workflow metadata and optionally content
242
329
  """
330
+ if not include_content and self._is_authority_run_id(workflow_id):
331
+ return await self.get_run_state(workflow_id)
332
+ endpoint = self._workflow_lookup_endpoint(workflow_id, include_content=include_content)
333
+ return await self._api(endpoint=endpoint, method="GET")
334
+
335
+ async def get_workflow(
336
+ self,
337
+ workflow_id: str,
338
+ include_content: bool = False,
339
+ ) -> Dict[str, Any]:
243
340
  endpoint = self._workflow_lookup_endpoint(workflow_id, include_content=include_content)
244
341
  return await self._api(endpoint=endpoint, method="GET")
245
342
 
@@ -693,15 +790,56 @@ class WorkflowNamespace:
693
790
  workflow_id: str,
694
791
  *,
695
792
  instance_id: Optional[str] = None,
696
- group: str,
697
- owner: str,
793
+ group: Optional[str] = None,
794
+ owner: Optional[str] = None,
698
795
  environment: Optional[str] = None,
699
796
  nudge_policy: Optional[Dict[str, Any]] = None,
700
797
  bindings: Optional[Dict[str, Any]] = None,
701
798
  mode: str = "blocking",
702
799
  context: Optional[Dict[str, Any]] = None,
800
+ workflow_ref: Optional[str] = None,
801
+ subject: Optional[str] = None,
802
+ company: Optional[str] = None,
803
+ inputs: Optional[Dict[str, Any]] = None,
804
+ target_project_id: Optional[str] = None,
805
+ target_environment: Optional[str] = None,
806
+ initiator_type: Optional[str] = None,
807
+ initiator_id: Optional[str] = None,
808
+ idempotency_key: Optional[str] = None,
703
809
  ) -> Dict[str, Any]:
704
810
  """Ensure a workflow instance exists and optionally launch execution."""
811
+ if (
812
+ subject is not None
813
+ or company is not None
814
+ or inputs is not None
815
+ or workflow_ref is not None
816
+ or target_project_id is not None
817
+ or target_environment is not None
818
+ or initiator_type is not None
819
+ or initiator_id is not None
820
+ or idempotency_key is not None
821
+ or group is None
822
+ or owner is None
823
+ ):
824
+ return await self._api(
825
+ endpoint="/api/authority/runs/ensure",
826
+ body=self._build_authority_ensure_body(
827
+ workflow_id,
828
+ workflow_ref=workflow_ref,
829
+ subject=subject,
830
+ company=company,
831
+ inputs=inputs,
832
+ context=context,
833
+ bindings=bindings,
834
+ instance_id=instance_id,
835
+ mode=mode,
836
+ target_project_id=target_project_id,
837
+ target_environment=target_environment,
838
+ initiator_type=initiator_type,
839
+ initiator_id=initiator_id,
840
+ idempotency_key=idempotency_key,
841
+ ),
842
+ )
705
843
  return await self.ensure_instance(
706
844
  workflow_id,
707
845
  instance_id=instance_id,
@@ -815,6 +953,50 @@ class WorkflowNamespace:
815
953
  )
816
954
  return result.get("instances", result) if isinstance(result, dict) else result
817
955
 
956
+ async def list_runs(
957
+ self,
958
+ *,
959
+ workflow_id: Optional[str] = None,
960
+ subject: Optional[str] = None,
961
+ company: Optional[str] = None,
962
+ status: Optional[str] = None,
963
+ limit: int = 50,
964
+ offset: int = 0,
965
+ target_project_id: Optional[str] = None,
966
+ target_environment: Optional[str] = None,
967
+ ) -> List[Dict[str, Any]]:
968
+ params = [f"limit={limit}", f"offset={offset}"]
969
+ if workflow_id:
970
+ params.append(f"workflow_id={workflow_id}")
971
+ if subject:
972
+ params.append(f"owner={subject}")
973
+ if company:
974
+ params.append(f"group={company}")
975
+ if status:
976
+ params.append(f"status={status}")
977
+ if target_project_id:
978
+ params.append(f"target_project_id={target_project_id}")
979
+ if target_environment:
980
+ params.append(f"target_environment={target_environment}")
981
+
982
+ result = await self._api(
983
+ endpoint=f"/api/authority/runs?{'&'.join(params)}",
984
+ method="GET",
985
+ )
986
+ return result.get("runs", result) if isinstance(result, dict) else result
987
+
988
+ async def get_run_state(self, run_id: str) -> Dict[str, Any]:
989
+ return await self._api(
990
+ endpoint=f"/api/authority/runs/{run_id}",
991
+ method="GET",
992
+ )
993
+
994
+ async def get_run_timeline(self, run_id: str) -> Dict[str, Any]:
995
+ return await self._api(
996
+ endpoint=f"/api/authority/runs/{run_id}/timeline",
997
+ method="GET",
998
+ )
999
+
818
1000
  async def get_instance_artifacts(self, workflow_id: str, instance_id: str) -> Dict[str, Any]:
819
1001
  """Get workflow instance artifact addresses."""
820
1002
  from urllib.parse import quote
@@ -855,13 +1037,35 @@ class WorkflowNamespace:
855
1037
  async def nudge(
856
1038
  self,
857
1039
  workflow_id: str,
858
- instance_id: str,
1040
+ instance_id: Optional[str] = None,
859
1041
  *,
860
1042
  reason: str,
861
1043
  trigger_artifact: Optional[str] = None,
862
1044
  mode: str = "async",
1045
+ target_project_id: Optional[str] = None,
1046
+ target_environment: Optional[str] = None,
1047
+ initiator_type: Optional[str] = None,
1048
+ initiator_id: Optional[str] = None,
1049
+ idempotency_key: Optional[str] = None,
863
1050
  ) -> Dict[str, Any]:
864
1051
  """Nudge a workflow instance into a new execution."""
1052
+ if instance_id is None and self._is_authority_run_id(workflow_id):
1053
+ body: Dict[str, Any] = {"reason": reason, "mode": mode}
1054
+ if trigger_artifact:
1055
+ body["trigger_artifact"] = trigger_artifact
1056
+ if target_project_id:
1057
+ body["target_project_id"] = target_project_id
1058
+ if target_environment:
1059
+ body["target_environment"] = target_environment
1060
+ return await self._api(
1061
+ endpoint=f"/api/authority/runs/{workflow_id}/nudge",
1062
+ body=self._authority_mutation_body(
1063
+ body,
1064
+ idempotency_key=idempotency_key,
1065
+ initiator_type=initiator_type,
1066
+ initiator_id=initiator_id,
1067
+ ),
1068
+ )
865
1069
  return await self.nudge_instance(
866
1070
  workflow_id,
867
1071
  instance_id,
@@ -921,13 +1125,35 @@ class WorkflowNamespace:
921
1125
  async def retry(
922
1126
  self,
923
1127
  workflow_id: str,
924
- instance_id: str,
1128
+ instance_id: Optional[str] = None,
925
1129
  *,
926
1130
  reason: str = "retry",
927
1131
  trigger_artifact: Optional[str] = None,
928
1132
  mode: str = "async",
1133
+ target_project_id: Optional[str] = None,
1134
+ target_environment: Optional[str] = None,
1135
+ initiator_type: Optional[str] = None,
1136
+ initiator_id: Optional[str] = None,
1137
+ idempotency_key: Optional[str] = None,
929
1138
  ) -> Dict[str, Any]:
930
1139
  """Retry a workflow instance. Alias for nudge with a default retry reason."""
1140
+ if instance_id is None and self._is_authority_run_id(workflow_id):
1141
+ body: Dict[str, Any] = {"reason": reason, "mode": mode}
1142
+ if trigger_artifact:
1143
+ body["trigger_artifact"] = trigger_artifact
1144
+ if target_project_id:
1145
+ body["target_project_id"] = target_project_id
1146
+ if target_environment:
1147
+ body["target_environment"] = target_environment
1148
+ return await self._api(
1149
+ endpoint=f"/api/authority/runs/{workflow_id}/retry",
1150
+ body=self._authority_mutation_body(
1151
+ body,
1152
+ idempotency_key=idempotency_key,
1153
+ initiator_type=initiator_type,
1154
+ initiator_id=initiator_id,
1155
+ ),
1156
+ )
931
1157
  return await self.retry_instance(
932
1158
  workflow_id,
933
1159
  instance_id,
@@ -1130,8 +1356,32 @@ class WorkflowNamespace:
1130
1356
  )
1131
1357
  return result.get("events", result) if isinstance(result, dict) else result
1132
1358
 
1133
- async def cancel(self, execution_id: str) -> Dict[str, Any]:
1134
- """Cancel a saved-workflow execution."""
1359
+ async def cancel(
1360
+ self,
1361
+ execution_id: str,
1362
+ *,
1363
+ target_project_id: Optional[str] = None,
1364
+ target_environment: Optional[str] = None,
1365
+ initiator_type: Optional[str] = None,
1366
+ initiator_id: Optional[str] = None,
1367
+ idempotency_key: Optional[str] = None,
1368
+ ) -> Dict[str, Any]:
1369
+ """Cancel a saved-workflow execution or Authority-backed run."""
1370
+ if self._is_authority_run_id(execution_id):
1371
+ body: Dict[str, Any] = {}
1372
+ if target_project_id:
1373
+ body["target_project_id"] = target_project_id
1374
+ if target_environment:
1375
+ body["target_environment"] = target_environment
1376
+ return await self._api(
1377
+ endpoint=f"/api/authority/runs/{execution_id}/cancel",
1378
+ body=self._authority_mutation_body(
1379
+ body,
1380
+ idempotency_key=idempotency_key,
1381
+ initiator_type=initiator_type,
1382
+ initiator_id=initiator_id,
1383
+ ),
1384
+ )
1135
1385
  return await self._api(
1136
1386
  endpoint=f"/api/workflow/executions/{execution_id}/cancel",
1137
1387
  body={},
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dominus-sdk-python
3
- Version: 2.17.0
3
+ Version: 2.18.0
4
4
  Summary: Python SDK for the Dominus Orchestrator Platform
5
5
  Author-email: CareBridge Systems <dev@carebridge.io>
6
6
  License: Proprietary
@@ -49,7 +49,7 @@ Async Python SDK for CareBridge Dominus platform services. Routes calls through
49
49
  - Server-side, asyncio-first Python SDK (3.9+)
50
50
  - Namespace API with root-level shortcuts for common operations
51
51
  - Targets production Cloudflare Workers (gateway, JWT, logs) and Cloud Run (orchestrator)
52
- - Version: 2.16.0
52
+ - Version: 2.18.0
53
53
 
54
54
  ## Quick Start
55
55
 
@@ -89,7 +89,15 @@ async for chunk in dominus.ai.stream_agent(
89
89
  ):
90
90
  print(chunk.get("content", ""), end="", flush=True)
91
91
 
92
- # Saved workflow lifecycle through workflow-manager
92
+ # Preferred Authority-backed one-call lifecycle
93
+ execution = await dominus.workflow.ensure(
94
+ "wf://carebridge/report-cycle",
95
+ subject="PCM47474562",
96
+ company="summit-radiology",
97
+ inputs={"report_snapshot": "ar://carebridge/summit-radiology/production/snapshot/report-1"},
98
+ )
99
+
100
+ # Saved workflow lifecycle through workflow-manager when you need explicit artifacts
93
101
  run = await dominus.workflow.create_run(
94
102
  workflow_id="wf_saved_123",
95
103
  context={"report_ref": {"report_id": "r-1", "accession": "a-1", "session_number": "2"}},
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "dominus-sdk-python"
7
- version = "2.17.0"
7
+ version = "2.18.0"
8
8
  description = "Python SDK for the Dominus Orchestrator Platform"
9
9
  readme = "README.md"
10
10
  license = {text = "Proprietary"}
@@ -1,4 +1,6 @@
1
1
  import pytest
2
+ import base64
3
+ import json
2
4
 
3
5
  from dominus.namespaces.ai import WorkflowSubNamespace
4
6
  from dominus.namespaces.workflow import WorkflowNamespace
@@ -53,6 +55,18 @@ class FakeClient:
53
55
  yield chunk
54
56
 
55
57
 
58
+ def authority_run_id(**overrides):
59
+ payload = {
60
+ "project_id": "proj-1",
61
+ "environment": "production",
62
+ "workflow_id": "wf-instance",
63
+ "instance_id": "inst-1",
64
+ }
65
+ payload.update(overrides)
66
+ encoded = base64.urlsafe_b64encode(json.dumps(payload).encode("utf-8")).decode("utf-8")
67
+ return encoded.rstrip("=")
68
+
69
+
56
70
  @pytest.mark.asyncio
57
71
  async def test_ai_workflow_create_run_requires_inline_definition():
58
72
  namespace = WorkflowSubNamespace(FakeClient())
@@ -253,6 +267,66 @@ async def test_saved_workflow_facade_lifecycle_contract_supports_processor_shape
253
267
  assert streamed == [{"_event": "workflow_start"}]
254
268
 
255
269
 
270
+ @pytest.mark.asyncio
271
+ async def test_workflow_ensure_supports_authority_one_call_lifecycle():
272
+ client = FakeClient()
273
+ namespace = WorkflowNamespace(client)
274
+
275
+ await namespace.ensure(
276
+ "wf://carebridge/report-cycle",
277
+ subject="PCM47474562",
278
+ company="summit-radiology",
279
+ inputs={"report_snapshot": "ar://artifact"},
280
+ target_project_id="proj-1",
281
+ target_environment="production",
282
+ )
283
+
284
+ assert client.calls[0]["endpoint"] == "/api/authority/runs/ensure"
285
+ body = client.calls[0]["body"]
286
+ assert body["workflow_ref"] == "wf://carebridge/report-cycle"
287
+ assert body["subject"] == "PCM47474562"
288
+ assert body["company"] == "summit-radiology"
289
+ assert body["inputs"] == {"report_snapshot": "ar://artifact"}
290
+ assert body["target_project_id"] == "proj-1"
291
+ assert body["target_environment"] == "production"
292
+ assert isinstance(body["idempotency_key"], str) and body["idempotency_key"]
293
+
294
+
295
+ @pytest.mark.asyncio
296
+ async def test_workflow_get_detects_authority_run_ids():
297
+ client = FakeClient()
298
+ namespace = WorkflowNamespace(client)
299
+ run_id = authority_run_id()
300
+
301
+ await namespace.get(run_id)
302
+
303
+ assert client.calls[0]["endpoint"] == f"/api/authority/runs/{run_id}"
304
+ assert client.calls[0]["method"] == "GET"
305
+
306
+
307
+ @pytest.mark.asyncio
308
+ async def test_workflow_retry_nudge_and_cancel_support_authority_run_ids():
309
+ client = FakeClient()
310
+ namespace = WorkflowNamespace(client)
311
+ run_id = authority_run_id()
312
+
313
+ await namespace.retry(run_id, reason="manual-retry")
314
+ await namespace.nudge(run_id, reason="new-input", trigger_artifact="artifact-1")
315
+ await namespace.cancel(run_id)
316
+
317
+ assert [call["endpoint"] for call in client.calls] == [
318
+ f"/api/authority/runs/{run_id}/retry",
319
+ f"/api/authority/runs/{run_id}/nudge",
320
+ f"/api/authority/runs/{run_id}/cancel",
321
+ ]
322
+ assert client.calls[0]["body"]["reason"] == "manual-retry"
323
+ assert client.calls[1]["body"]["reason"] == "new-input"
324
+ assert client.calls[1]["body"]["trigger_artifact"] == "artifact-1"
325
+ assert isinstance(client.calls[0]["body"]["idempotency_key"], str)
326
+ assert isinstance(client.calls[1]["body"]["idempotency_key"], str)
327
+ assert isinstance(client.calls[2]["body"]["idempotency_key"], str)
328
+
329
+
256
330
  @pytest.mark.asyncio
257
331
  async def test_execute_pipeline_routes_to_native_pipeline_runner():
258
332
  client = FakeClient()