dominus-sdk-python 5.0.0__tar.gz → 6.0.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.
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/PKG-INFO +4 -4
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/README.md +3 -3
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/dominus/__init__.py +1 -7
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/artifacts.py +10 -27
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/authority.py +33 -12
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/recipes.py +24 -8
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/workflow.py +49 -26
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/dominus_sdk_python.egg-info/PKG-INFO +4 -4
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/pyproject.toml +1 -1
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/tests/test_authority_public_vocabulary.py +43 -3
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/tests/test_recipes_namespace.py +36 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/tests/test_workflow_lifecycle.py +23 -6
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/tests/test_workflow_refs.py +47 -9
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/dominus/config/__init__.py +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/dominus/config/endpoints.py +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/dominus/errors.py +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/dominus/helpers/__init__.py +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/dominus/helpers/auth.py +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/dominus/helpers/cache.py +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/dominus/helpers/console_capture.py +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/dominus/helpers/core.py +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/dominus/helpers/crypto.py +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/dominus/helpers/sse.py +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/dominus/helpers/trace.py +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/__init__.py +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/admin.py +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/ai.py +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/auth.py +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/browser.py +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/courier.py +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/db.py +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/ddl.py +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/deployer.py +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/fastapi.py +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/files.py +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/health.py +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/jobs.py +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/logs.py +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/portal.py +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/processor.py +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/redis.py +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/secrets.py +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/secure.py +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/stash.py +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/sync.py +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/warden.py +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/dominus/services/__init__.py +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/dominus/start.py +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/dominus_sdk_python.egg-info/SOURCES.txt +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/dominus_sdk_python.egg-info/dependency_links.txt +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/dominus_sdk_python.egg-info/requires.txt +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/dominus_sdk_python.egg-info/top_level.txt +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/setup.cfg +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/tests/test_auth.py +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/tests/test_browser_namespace.py +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/tests/test_control_plane_namespaces.py +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/tests/test_errors.py +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/tests/test_flat_commands.py +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/tests/test_health.py +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/tests/test_logs.py +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/tests/test_provisioning_parity.py +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/tests/test_public_exports.py +0 -0
- {dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/tests/test_transport_compat.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dominus-sdk-python
|
|
3
|
-
Version:
|
|
3
|
+
Version: 6.0.0
|
|
4
4
|
Summary: Python SDK for the Dominus gateway-first platform
|
|
5
5
|
Author-email: CareBridge Systems <dev@carebridge.io>
|
|
6
6
|
License: Proprietary
|
|
@@ -43,7 +43,7 @@ Async Python SDK for the Dominus gateway-first service plane.
|
|
|
43
43
|
- Gateway-scoped client mode for MCP and other user-JWT sessions
|
|
44
44
|
- Transport compatibility for wrapped `{success,data}` responses and unwrapped Warden/control-plane success objects
|
|
45
45
|
- Local helpers for JWT verification, trace propagation, retries, and console capture
|
|
46
|
-
- Current package version: `
|
|
46
|
+
- Current package version: `6.0.0`
|
|
47
47
|
|
|
48
48
|
## Install
|
|
49
49
|
|
|
@@ -66,7 +66,7 @@ users = await dominus.db.query("users", filters={"status": "active"})
|
|
|
66
66
|
await dominus.redis.set("session:123", {"user": "john"}, ttl=3600)
|
|
67
67
|
|
|
68
68
|
run = await dominus.workflow.ensure(
|
|
69
|
-
"
|
|
69
|
+
workflow_recipe_ref="recipe://workflow-recipe-v1/report-cycle@v3",
|
|
70
70
|
subject="PCM47474562",
|
|
71
71
|
company="summit-radiology",
|
|
72
72
|
)
|
|
@@ -193,7 +193,7 @@ JWT and selected scope headers directly through Gateway.
|
|
|
193
193
|
| `health` | Gateway | Health and ping helpers |
|
|
194
194
|
| `admin` | Admin Worker | Admin category reseed/reset |
|
|
195
195
|
| `ai` | Agent Runtime | Agent, completion, RAG, artifacts, results, raw orchestration |
|
|
196
|
-
| `workflow` | Workflow Manager + Authority | Saved workflow CRUD and
|
|
196
|
+
| `workflow` | Workflow Manager + Authority | Saved workflow CRUD and recipe-backed run lifecycle |
|
|
197
197
|
| `artifacts` | Artifact Worker | Addressed V2 artifacts, bookmarks, watches |
|
|
198
198
|
| `jobs` | Job Worker | Enqueue, poll, dead-letter management |
|
|
199
199
|
| `processor` | Processor | Batch and single-job processing |
|
|
@@ -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: `
|
|
12
|
+
- Current package version: `6.0.0`
|
|
13
13
|
|
|
14
14
|
## Install
|
|
15
15
|
|
|
@@ -32,7 +32,7 @@ users = await dominus.db.query("users", filters={"status": "active"})
|
|
|
32
32
|
await dominus.redis.set("session:123", {"user": "john"}, ttl=3600)
|
|
33
33
|
|
|
34
34
|
run = await dominus.workflow.ensure(
|
|
35
|
-
"
|
|
35
|
+
workflow_recipe_ref="recipe://workflow-recipe-v1/report-cycle@v3",
|
|
36
36
|
subject="PCM47474562",
|
|
37
37
|
company="summit-radiology",
|
|
38
38
|
)
|
|
@@ -159,7 +159,7 @@ JWT and selected scope headers directly through Gateway.
|
|
|
159
159
|
| `health` | Gateway | Health and ping helpers |
|
|
160
160
|
| `admin` | Admin Worker | Admin category reseed/reset |
|
|
161
161
|
| `ai` | Agent Runtime | Agent, completion, RAG, artifacts, results, raw orchestration |
|
|
162
|
-
| `workflow` | Workflow Manager + Authority | Saved workflow CRUD and
|
|
162
|
+
| `workflow` | Workflow Manager + Authority | Saved workflow CRUD and recipe-backed run lifecycle |
|
|
163
163
|
| `artifacts` | Artifact Worker | Addressed V2 artifacts, bookmarks, watches |
|
|
164
164
|
| `jobs` | Job Worker | Enqueue, poll, dead-letter management |
|
|
165
165
|
| `processor` | Processor | Batch and single-job processing |
|
|
@@ -90,14 +90,12 @@ from .namespaces.health import HealthNamespace
|
|
|
90
90
|
# Export new namespaces (Node.js SDK parity)
|
|
91
91
|
from .namespaces.artifacts import (
|
|
92
92
|
ARTIFACT_REF_PREFIX,
|
|
93
|
-
DISPLAY_REF_PREFIX,
|
|
94
93
|
ARTIFACT_ENVIRONMENTS,
|
|
95
94
|
ArtifactsNamespace,
|
|
96
95
|
build_artifact_ref,
|
|
97
96
|
build_v2_artifact_ref,
|
|
98
97
|
build_pinned_artifact_ref,
|
|
99
98
|
build_legacy_artifact_ref,
|
|
100
|
-
build_display_artifact_ref,
|
|
101
99
|
parse_artifact_ref,
|
|
102
100
|
try_parse_artifact_ref,
|
|
103
101
|
validate_artifact_address,
|
|
@@ -105,7 +103,6 @@ from .namespaces.artifacts import (
|
|
|
105
103
|
is_pinned_artifact_ref,
|
|
106
104
|
is_head_artifact_ref,
|
|
107
105
|
is_legacy_artifact_ref,
|
|
108
|
-
is_display_artifact_ref,
|
|
109
106
|
)
|
|
110
107
|
from .namespaces.jobs import JobsNamespace
|
|
111
108
|
from .namespaces.processor import ProcessorNamespace
|
|
@@ -165,7 +162,7 @@ from .errors import (
|
|
|
165
162
|
TimeoutError as DominusTimeoutError,
|
|
166
163
|
)
|
|
167
164
|
|
|
168
|
-
__version__ = "
|
|
165
|
+
__version__ = "6.0.0"
|
|
169
166
|
__all__ = [
|
|
170
167
|
# Main SDK instance
|
|
171
168
|
"dominus",
|
|
@@ -195,14 +192,12 @@ __all__ = [
|
|
|
195
192
|
"HealthNamespace",
|
|
196
193
|
# New namespaces (Node.js SDK parity)
|
|
197
194
|
"ARTIFACT_REF_PREFIX",
|
|
198
|
-
"DISPLAY_REF_PREFIX",
|
|
199
195
|
"ARTIFACT_ENVIRONMENTS",
|
|
200
196
|
"ArtifactsNamespace",
|
|
201
197
|
"build_artifact_ref",
|
|
202
198
|
"build_v2_artifact_ref",
|
|
203
199
|
"build_pinned_artifact_ref",
|
|
204
200
|
"build_legacy_artifact_ref",
|
|
205
|
-
"build_display_artifact_ref",
|
|
206
201
|
"parse_artifact_ref",
|
|
207
202
|
"try_parse_artifact_ref",
|
|
208
203
|
"validate_artifact_address",
|
|
@@ -210,7 +205,6 @@ __all__ = [
|
|
|
210
205
|
"is_pinned_artifact_ref",
|
|
211
206
|
"is_head_artifact_ref",
|
|
212
207
|
"is_legacy_artifact_ref",
|
|
213
|
-
"is_display_artifact_ref",
|
|
214
208
|
"JobsNamespace",
|
|
215
209
|
"ProcessorNamespace",
|
|
216
210
|
"SyncNamespace",
|
|
@@ -35,10 +35,13 @@ def _safe_string(value: Any) -> str:
|
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
ARTIFACT_REF_PREFIX = "ar://"
|
|
38
|
-
DISPLAY_REF_PREFIX = "art:r:"
|
|
39
38
|
ARTIFACT_ENVIRONMENTS = ("development", "staging", "production")
|
|
40
39
|
|
|
41
40
|
|
|
41
|
+
def _legacy_project_head_ref(project_id: str, environment: str, key: str) -> str:
|
|
42
|
+
return f"{ARTIFACT_REF_PREFIX}dominus/project:{project_id}/{environment}/legacy/{quote(key, safe='')}"
|
|
43
|
+
|
|
44
|
+
|
|
42
45
|
def build_artifact_ref(
|
|
43
46
|
*,
|
|
44
47
|
project_slug: Optional[str] = None,
|
|
@@ -109,13 +112,6 @@ def build_pinned_artifact_ref(
|
|
|
109
112
|
return f"{ref}@v{version}"
|
|
110
113
|
|
|
111
114
|
|
|
112
|
-
def build_display_artifact_ref(artifact_key: str) -> Optional[str]:
|
|
113
|
-
normalized_key = _safe_string(artifact_key)
|
|
114
|
-
if not normalized_key:
|
|
115
|
-
return None
|
|
116
|
-
return f"{DISPLAY_REF_PREFIX}{normalized_key}"
|
|
117
|
-
|
|
118
|
-
|
|
119
115
|
def build_legacy_artifact_ref(
|
|
120
116
|
*,
|
|
121
117
|
project_slug: str,
|
|
@@ -146,15 +142,6 @@ def parse_artifact_ref(artifact_ref: str) -> Optional[Dict[str, Any]]:
|
|
|
146
142
|
normalized = _safe_string(artifact_ref)
|
|
147
143
|
if not normalized:
|
|
148
144
|
return None
|
|
149
|
-
if normalized.startswith(DISPLAY_REF_PREFIX):
|
|
150
|
-
artifact_key = normalized[len(DISPLAY_REF_PREFIX):]
|
|
151
|
-
if not artifact_key:
|
|
152
|
-
return None
|
|
153
|
-
return {
|
|
154
|
-
"format": "display",
|
|
155
|
-
"raw": normalized,
|
|
156
|
-
"artifact_key": artifact_key,
|
|
157
|
-
}
|
|
158
145
|
if not normalized.startswith(ARTIFACT_REF_PREFIX):
|
|
159
146
|
return None
|
|
160
147
|
|
|
@@ -245,10 +232,6 @@ def is_legacy_artifact_ref(artifact_ref: Dict[str, Any]) -> bool:
|
|
|
245
232
|
return artifact_ref.get("format") == "v1"
|
|
246
233
|
|
|
247
234
|
|
|
248
|
-
def is_display_artifact_ref(artifact_ref: Dict[str, Any]) -> bool:
|
|
249
|
-
return artifact_ref.get("format") == "display"
|
|
250
|
-
|
|
251
|
-
|
|
252
235
|
def _split_version_suffix(value: str) -> tuple[str, Optional[str]]:
|
|
253
236
|
if "@" not in value:
|
|
254
237
|
return value, None
|
|
@@ -366,10 +349,10 @@ class ArtifactsNamespace:
|
|
|
366
349
|
category=category,
|
|
367
350
|
content_type=content_type,
|
|
368
351
|
)
|
|
369
|
-
|
|
352
|
+
fallback_ref = _legacy_project_head_ref(project_id, environment, use_key)
|
|
370
353
|
return {
|
|
371
354
|
"key": use_key,
|
|
372
|
-
"ref": v2.get("
|
|
355
|
+
"ref": v2.get("head_ref") or fallback_ref,
|
|
373
356
|
"storage_type": v2.get("storage_type") or "redis",
|
|
374
357
|
"size_bytes": v2.get("size_bytes", 0),
|
|
375
358
|
"expires_at": v2.get("expires_at") or "",
|
|
@@ -377,14 +360,14 @@ class ArtifactsNamespace:
|
|
|
377
360
|
|
|
378
361
|
async def retrieve(self, key: str) -> Dict[str, Any]:
|
|
379
362
|
"""
|
|
380
|
-
Retrieve by key via
|
|
363
|
+
Retrieve by key via the addressed V2 compatibility head.
|
|
381
364
|
"""
|
|
382
|
-
project_id,
|
|
365
|
+
project_id, environment = await self._legacy_project_context()
|
|
366
|
+
ref = _legacy_project_head_ref(project_id, environment, key)
|
|
383
367
|
return await self._api(
|
|
384
368
|
"/api/artifact/v2/retrieve",
|
|
385
369
|
body={
|
|
386
|
-
"ref":
|
|
387
|
-
"target_project_id": project_id,
|
|
370
|
+
"ref": ref,
|
|
388
371
|
},
|
|
389
372
|
)
|
|
390
373
|
|
|
@@ -12,9 +12,9 @@ All methods route through the gateway (``use_gateway=True``) which maps
|
|
|
12
12
|
|
|
13
13
|
Run launch is also exposed as ``dominus.workflow.ensure(...)``. Both call
|
|
14
14
|
``/api/authority/runs/ensure``. Use ``dominus.workflow.ensure`` for the
|
|
15
|
-
canonical
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
canonical recipe-backed launch path; use ``dominus.authority.ensure_run`` when
|
|
16
|
+
you want to think in Authority terms (run lifecycle, run dossiers, retries,
|
|
17
|
+
cancels).
|
|
18
18
|
"""
|
|
19
19
|
from __future__ import annotations
|
|
20
20
|
|
|
@@ -48,6 +48,14 @@ def _is_provisioning_bootstrap_run_kind(run_kind: Optional[str]) -> bool:
|
|
|
48
48
|
return _normalize_run_kind_token(run_kind) == "provisioning_bootstrap"
|
|
49
49
|
|
|
50
50
|
|
|
51
|
+
def _is_workflow_recipe_ref(value: Optional[str]) -> bool:
|
|
52
|
+
return str(value or "").strip().startswith("recipe://workflow-recipe-v1/")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _is_pipeline_recipe_ref(value: Optional[str]) -> bool:
|
|
56
|
+
return str(value or "").strip().startswith("recipe://pipeline-recipe-v1/")
|
|
57
|
+
|
|
58
|
+
|
|
51
59
|
def _query_string(params: Mapping[str, Any]) -> str:
|
|
52
60
|
cleaned = {k: v for k, v in params.items() if v is not None and v != ""}
|
|
53
61
|
if not cleaned:
|
|
@@ -61,7 +69,10 @@ class AuthorityNamespace:
|
|
|
61
69
|
|
|
62
70
|
Usage::
|
|
63
71
|
|
|
64
|
-
await dominus.authority.ensure_run(
|
|
72
|
+
await dominus.authority.ensure_run(
|
|
73
|
+
workflow_recipe_ref="recipe://workflow-recipe-v1/report-cycle@head",
|
|
74
|
+
subject="PCM47474562",
|
|
75
|
+
)
|
|
65
76
|
await dominus.authority.list_provisioning_targets()
|
|
66
77
|
await dominus.authority.get_run_dossier(run_id)
|
|
67
78
|
"""
|
|
@@ -167,8 +178,8 @@ class AuthorityNamespace:
|
|
|
167
178
|
async def ensure_run(
|
|
168
179
|
self,
|
|
169
180
|
*,
|
|
170
|
-
|
|
171
|
-
|
|
181
|
+
workflow_recipe_ref: Optional[str] = None,
|
|
182
|
+
pipeline_recipe_ref: Optional[str] = None,
|
|
172
183
|
run_kind: Optional[str] = None,
|
|
173
184
|
bootstrap_profile_ref: Optional[str] = None,
|
|
174
185
|
provisioning_target_slug: Optional[str] = None,
|
|
@@ -213,11 +224,23 @@ class AuthorityNamespace:
|
|
|
213
224
|
``bootstrap_provisioning_target`` (``execution_mode``, ``region``, ``bootstrap_profile_ref``, ...).
|
|
214
225
|
"""
|
|
215
226
|
bootstrap = _is_provisioning_bootstrap_run_kind(run_kind)
|
|
216
|
-
|
|
227
|
+
recipe_sources = [
|
|
228
|
+
value
|
|
229
|
+
for value in (
|
|
230
|
+
str(workflow_recipe_ref or "").strip(),
|
|
231
|
+
str(pipeline_recipe_ref or "").strip(),
|
|
232
|
+
)
|
|
233
|
+
if value
|
|
234
|
+
]
|
|
235
|
+
if not bootstrap and len(recipe_sources) != 1:
|
|
217
236
|
raise ValueError(
|
|
218
|
-
"ensure_run requires
|
|
237
|
+
"ensure_run requires exactly one of workflow_recipe_ref or pipeline_recipe_ref unless run_kind is "
|
|
219
238
|
"provisioning.bootstrap"
|
|
220
239
|
)
|
|
240
|
+
if workflow_recipe_ref and not _is_workflow_recipe_ref(workflow_recipe_ref):
|
|
241
|
+
raise ValueError("workflow_recipe_ref must start with recipe://workflow-recipe-v1/")
|
|
242
|
+
if pipeline_recipe_ref and not _is_pipeline_recipe_ref(pipeline_recipe_ref):
|
|
243
|
+
raise ValueError("pipeline_recipe_ref must start with recipe://pipeline-recipe-v1/")
|
|
221
244
|
if bootstrap:
|
|
222
245
|
target_slug = (provisioning_target_slug or "").strip()
|
|
223
246
|
if not target_slug:
|
|
@@ -236,8 +259,6 @@ class AuthorityNamespace:
|
|
|
236
259
|
"run_kind": run_kind,
|
|
237
260
|
"bootstrap_profile_ref": bootstrap_profile_ref,
|
|
238
261
|
"provisioning_target_slug": provisioning_target_slug,
|
|
239
|
-
"workflow_id": workflow_id,
|
|
240
|
-
"workflow_ref": workflow_ref,
|
|
241
262
|
"instance_id": instance_id,
|
|
242
263
|
"instance_key": instance_key,
|
|
243
264
|
"group": group,
|
|
@@ -262,8 +283,8 @@ class AuthorityNamespace:
|
|
|
262
283
|
})
|
|
263
284
|
else:
|
|
264
285
|
body = _compact({
|
|
265
|
-
"
|
|
266
|
-
"
|
|
286
|
+
"workflow_recipe_ref": workflow_recipe_ref,
|
|
287
|
+
"pipeline_recipe_ref": pipeline_recipe_ref,
|
|
267
288
|
"instance_id": instance_id,
|
|
268
289
|
"instance_key": instance_key,
|
|
269
290
|
"group": group,
|
|
@@ -41,14 +41,24 @@ class RecipesNamespace:
|
|
|
41
41
|
def __init__(self, client: "Dominus"):
|
|
42
42
|
self._client = client
|
|
43
43
|
|
|
44
|
-
async def _post(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
44
|
+
async def _post(
|
|
45
|
+
self,
|
|
46
|
+
endpoint: str,
|
|
47
|
+
body: Optional[Dict[str, Any]] = None,
|
|
48
|
+
*,
|
|
49
|
+
timeout: float = 30.0,
|
|
50
|
+
actor_context: Optional[Dict[str, str]] = None,
|
|
51
|
+
) -> Dict[str, Any]:
|
|
52
|
+
kwargs: Dict[str, Any] = {
|
|
53
|
+
"endpoint": endpoint,
|
|
54
|
+
"method": "POST",
|
|
55
|
+
"body": body or {},
|
|
56
|
+
"use_gateway": True,
|
|
57
|
+
"timeout": timeout,
|
|
58
|
+
}
|
|
59
|
+
if actor_context is not None:
|
|
60
|
+
kwargs["actor"] = actor_context
|
|
61
|
+
return await self._client._request(**kwargs)
|
|
52
62
|
|
|
53
63
|
async def _get(self, endpoint: str, *, timeout: float = 30.0) -> Dict[str, Any]:
|
|
54
64
|
return await self._client._request(
|
|
@@ -75,6 +85,7 @@ class RecipesNamespace:
|
|
|
75
85
|
name: str,
|
|
76
86
|
body: str,
|
|
77
87
|
description: Optional[str] = None,
|
|
88
|
+
actor_context: Optional[Dict[str, str]] = None,
|
|
78
89
|
timeout: float = 30.0,
|
|
79
90
|
) -> Dict[str, Any]:
|
|
80
91
|
"""Publish a recipe. ``POST /api/recipe/recipes/publish``."""
|
|
@@ -87,6 +98,7 @@ class RecipesNamespace:
|
|
|
87
98
|
"body": body,
|
|
88
99
|
"description": description,
|
|
89
100
|
}),
|
|
101
|
+
actor_context=actor_context,
|
|
90
102
|
timeout=timeout,
|
|
91
103
|
)
|
|
92
104
|
|
|
@@ -112,6 +124,7 @@ class RecipesNamespace:
|
|
|
112
124
|
name: str,
|
|
113
125
|
version: int,
|
|
114
126
|
reason: Optional[str] = None,
|
|
127
|
+
actor_context: Optional[Dict[str, str]] = None,
|
|
115
128
|
timeout: float = 30.0,
|
|
116
129
|
) -> Dict[str, Any]:
|
|
117
130
|
"""Mark the current head recipe version deprecated. ``POST /api/recipe/recipes/deprecate``."""
|
|
@@ -124,6 +137,7 @@ class RecipesNamespace:
|
|
|
124
137
|
"version": version,
|
|
125
138
|
"reason": reason,
|
|
126
139
|
}),
|
|
140
|
+
actor_context=actor_context,
|
|
127
141
|
timeout=timeout,
|
|
128
142
|
)
|
|
129
143
|
|
|
@@ -170,6 +184,7 @@ class RecipesNamespace:
|
|
|
170
184
|
owning_worker: str,
|
|
171
185
|
schema_version: int,
|
|
172
186
|
json_schema: Dict[str, Any],
|
|
187
|
+
actor_context: Optional[Dict[str, str]] = None,
|
|
173
188
|
timeout: float = 15.0,
|
|
174
189
|
) -> Dict[str, Any]:
|
|
175
190
|
"""Register a recipe type. ``POST /api/recipe/types/register``.
|
|
@@ -184,5 +199,6 @@ class RecipesNamespace:
|
|
|
184
199
|
"schema_version": schema_version,
|
|
185
200
|
"json_schema": json_schema,
|
|
186
201
|
},
|
|
202
|
+
actor_context=actor_context,
|
|
187
203
|
timeout=timeout,
|
|
188
204
|
)
|
|
@@ -33,9 +33,9 @@ Usage:
|
|
|
33
33
|
tools = await dominus.workflow.list_tools(limit=50)
|
|
34
34
|
await dominus.workflow.register_tool({"slug": "my-tool", "name": "My Tool", "tool_type": "http"})
|
|
35
35
|
|
|
36
|
-
# Canonical
|
|
36
|
+
# Canonical recipe-backed launch (Authority POST /api/authority/runs/ensure)
|
|
37
37
|
execution = await dominus.workflow.ensure(
|
|
38
|
-
"my-workflow
|
|
38
|
+
workflow_recipe_ref="recipe://workflow-recipe-v1/my-workflow@head",
|
|
39
39
|
subject="PCM47474562",
|
|
40
40
|
company="acme",
|
|
41
41
|
inputs={"key": "value"},
|
|
@@ -58,7 +58,7 @@ class WorkflowNamespace:
|
|
|
58
58
|
Workflow management namespace.
|
|
59
59
|
|
|
60
60
|
Provides operations for workflow CRUD, pipelines, templates,
|
|
61
|
-
and the
|
|
61
|
+
and the recipe-backed run lifecycle via Authority.
|
|
62
62
|
"""
|
|
63
63
|
|
|
64
64
|
def __init__(self, client: "Dominus"):
|
|
@@ -92,17 +92,12 @@ class WorkflowNamespace:
|
|
|
92
92
|
return endpoint
|
|
93
93
|
|
|
94
94
|
@staticmethod
|
|
95
|
-
def
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if WorkflowNamespace._is_workflow_ref(workflow_id):
|
|
102
|
-
return {"workflow_ref": str(workflow_id).strip()}
|
|
103
|
-
if workflow_id and str(workflow_id).strip():
|
|
104
|
-
return {"workflow_id": str(workflow_id).strip()}
|
|
105
|
-
raise ValueError("workflow_id or workflow_ref is required")
|
|
95
|
+
def _is_workflow_recipe_ref(value: Optional[str]) -> bool:
|
|
96
|
+
return str(value or "").strip().startswith("recipe://workflow-recipe-v1/")
|
|
97
|
+
|
|
98
|
+
@staticmethod
|
|
99
|
+
def _is_pipeline_recipe_ref(value: Optional[str]) -> bool:
|
|
100
|
+
return str(value or "").strip().startswith("recipe://pipeline-recipe-v1/")
|
|
106
101
|
|
|
107
102
|
@staticmethod
|
|
108
103
|
def _instance_base_endpoint(workflow_id: str) -> str:
|
|
@@ -210,11 +205,14 @@ class WorkflowNamespace:
|
|
|
210
205
|
|
|
211
206
|
def _build_authority_ensure_body(
|
|
212
207
|
self,
|
|
213
|
-
|
|
208
|
+
recipe_ref: Optional[str] = None,
|
|
214
209
|
*,
|
|
215
|
-
|
|
210
|
+
workflow_recipe_ref: Optional[str] = None,
|
|
211
|
+
pipeline_recipe_ref: Optional[str] = None,
|
|
216
212
|
subject: Optional[str] = None,
|
|
217
213
|
company: Optional[str] = None,
|
|
214
|
+
group: Optional[str] = None,
|
|
215
|
+
owner: Optional[str] = None,
|
|
218
216
|
inputs: Optional[Dict[str, Any]] = None,
|
|
219
217
|
context: Optional[Dict[str, Any]] = None,
|
|
220
218
|
bindings: Optional[Dict[str, Any]] = None,
|
|
@@ -227,16 +225,37 @@ class WorkflowNamespace:
|
|
|
227
225
|
idempotency_key: Optional[str] = None,
|
|
228
226
|
) -> Dict[str, Any]:
|
|
229
227
|
body: Dict[str, Any] = {"mode": mode}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
228
|
+
launch_sources: List[tuple[str, str]] = []
|
|
229
|
+
normalized_recipe_ref = str(recipe_ref or "").strip()
|
|
230
|
+
if workflow_recipe_ref and str(workflow_recipe_ref).strip():
|
|
231
|
+
launch_sources.append(("workflow_recipe_ref", str(workflow_recipe_ref).strip()))
|
|
232
|
+
if pipeline_recipe_ref and str(pipeline_recipe_ref).strip():
|
|
233
|
+
launch_sources.append(("pipeline_recipe_ref", str(pipeline_recipe_ref).strip()))
|
|
234
|
+
if normalized_recipe_ref:
|
|
235
|
+
if self._is_workflow_recipe_ref(normalized_recipe_ref):
|
|
236
|
+
launch_sources.append(("workflow_recipe_ref", normalized_recipe_ref))
|
|
237
|
+
elif self._is_pipeline_recipe_ref(normalized_recipe_ref):
|
|
238
|
+
launch_sources.append(("pipeline_recipe_ref", normalized_recipe_ref))
|
|
239
|
+
else:
|
|
240
|
+
raise ValueError(
|
|
241
|
+
"recipe_ref must start with recipe://workflow-recipe-v1/ "
|
|
242
|
+
"or recipe://pipeline-recipe-v1/"
|
|
243
|
+
)
|
|
244
|
+
if len(launch_sources) != 1:
|
|
245
|
+
raise ValueError(
|
|
246
|
+
"ensure requires exactly one of recipe_ref, workflow_recipe_ref, "
|
|
247
|
+
"or pipeline_recipe_ref"
|
|
248
|
+
)
|
|
249
|
+
field, value = launch_sources[0]
|
|
250
|
+
body[field] = value
|
|
236
251
|
if subject is not None:
|
|
237
252
|
body["subject"] = subject
|
|
238
253
|
if company is not None:
|
|
239
254
|
body["company"] = company
|
|
255
|
+
if group is not None:
|
|
256
|
+
body["group"] = group
|
|
257
|
+
if owner is not None:
|
|
258
|
+
body["owner"] = owner
|
|
240
259
|
if inputs:
|
|
241
260
|
body["inputs"] = inputs
|
|
242
261
|
if context:
|
|
@@ -855,7 +874,7 @@ class WorkflowNamespace:
|
|
|
855
874
|
|
|
856
875
|
async def ensure(
|
|
857
876
|
self,
|
|
858
|
-
|
|
877
|
+
recipe_ref: Optional[str] = None,
|
|
859
878
|
*,
|
|
860
879
|
instance_id: Optional[str] = None,
|
|
861
880
|
group: Optional[str] = None,
|
|
@@ -865,7 +884,8 @@ class WorkflowNamespace:
|
|
|
865
884
|
bindings: Optional[Dict[str, Any]] = None,
|
|
866
885
|
mode: str = "blocking",
|
|
867
886
|
context: Optional[Dict[str, Any]] = None,
|
|
868
|
-
|
|
887
|
+
workflow_recipe_ref: Optional[str] = None,
|
|
888
|
+
pipeline_recipe_ref: Optional[str] = None,
|
|
869
889
|
subject: Optional[str] = None,
|
|
870
890
|
company: Optional[str] = None,
|
|
871
891
|
inputs: Optional[Dict[str, Any]] = None,
|
|
@@ -884,10 +904,13 @@ class WorkflowNamespace:
|
|
|
884
904
|
return await self._api(
|
|
885
905
|
endpoint="/api/authority/runs/ensure",
|
|
886
906
|
body=self._build_authority_ensure_body(
|
|
887
|
-
|
|
888
|
-
|
|
907
|
+
recipe_ref,
|
|
908
|
+
workflow_recipe_ref=workflow_recipe_ref,
|
|
909
|
+
pipeline_recipe_ref=pipeline_recipe_ref,
|
|
889
910
|
subject=subject,
|
|
890
911
|
company=company,
|
|
912
|
+
group=group,
|
|
913
|
+
owner=owner,
|
|
891
914
|
inputs=inputs,
|
|
892
915
|
context=context,
|
|
893
916
|
bindings=bindings,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dominus-sdk-python
|
|
3
|
-
Version:
|
|
3
|
+
Version: 6.0.0
|
|
4
4
|
Summary: Python SDK for the Dominus gateway-first platform
|
|
5
5
|
Author-email: CareBridge Systems <dev@carebridge.io>
|
|
6
6
|
License: Proprietary
|
|
@@ -43,7 +43,7 @@ Async Python SDK for the Dominus gateway-first service plane.
|
|
|
43
43
|
- Gateway-scoped client mode for MCP and other user-JWT sessions
|
|
44
44
|
- Transport compatibility for wrapped `{success,data}` responses and unwrapped Warden/control-plane success objects
|
|
45
45
|
- Local helpers for JWT verification, trace propagation, retries, and console capture
|
|
46
|
-
- Current package version: `
|
|
46
|
+
- Current package version: `6.0.0`
|
|
47
47
|
|
|
48
48
|
## Install
|
|
49
49
|
|
|
@@ -66,7 +66,7 @@ users = await dominus.db.query("users", filters={"status": "active"})
|
|
|
66
66
|
await dominus.redis.set("session:123", {"user": "john"}, ttl=3600)
|
|
67
67
|
|
|
68
68
|
run = await dominus.workflow.ensure(
|
|
69
|
-
"
|
|
69
|
+
workflow_recipe_ref="recipe://workflow-recipe-v1/report-cycle@v3",
|
|
70
70
|
subject="PCM47474562",
|
|
71
71
|
company="summit-radiology",
|
|
72
72
|
)
|
|
@@ -193,7 +193,7 @@ JWT and selected scope headers directly through Gateway.
|
|
|
193
193
|
| `health` | Gateway | Health and ping helpers |
|
|
194
194
|
| `admin` | Admin Worker | Admin category reseed/reset |
|
|
195
195
|
| `ai` | Agent Runtime | Agent, completion, RAG, artifacts, results, raw orchestration |
|
|
196
|
-
| `workflow` | Workflow Manager + Authority | Saved workflow CRUD and
|
|
196
|
+
| `workflow` | Workflow Manager + Authority | Saved workflow CRUD and recipe-backed run lifecycle |
|
|
197
197
|
| `artifacts` | Artifact Worker | Addressed V2 artifacts, bookmarks, watches |
|
|
198
198
|
| `jobs` | Job Worker | Enqueue, poll, dead-letter management |
|
|
199
199
|
| `processor` | Processor | Batch and single-job processing |
|
{dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/tests/test_authority_public_vocabulary.py
RENAMED
|
@@ -86,10 +86,29 @@ def test_authority_scope_signatures_use_new_public_vocabulary():
|
|
|
86
86
|
schedule_create_sig = inspect.signature(AuthorityNamespace.create_schedule)
|
|
87
87
|
mint_sig = inspect.signature(core_helpers.mint_selected_scope_jwt)
|
|
88
88
|
|
|
89
|
-
for name in (
|
|
89
|
+
for name in (
|
|
90
|
+
"app_slug",
|
|
91
|
+
"env",
|
|
92
|
+
"target_org_id",
|
|
93
|
+
"target_env",
|
|
94
|
+
"run_kind",
|
|
95
|
+
"target_app_slug",
|
|
96
|
+
"bootstrap_profile_ref",
|
|
97
|
+
"provisioning_target_slug",
|
|
98
|
+
"workflow_recipe_ref",
|
|
99
|
+
"pipeline_recipe_ref",
|
|
100
|
+
):
|
|
90
101
|
assert name in ensure_sig.parameters
|
|
91
102
|
old_profile_key = "bootstrap" + "_profile"
|
|
92
|
-
for legacy in (
|
|
103
|
+
for legacy in (
|
|
104
|
+
"project_slug",
|
|
105
|
+
"environment",
|
|
106
|
+
"target_project_id",
|
|
107
|
+
"target_environment",
|
|
108
|
+
"workflow_id",
|
|
109
|
+
"workflow_ref",
|
|
110
|
+
old_profile_key,
|
|
111
|
+
):
|
|
93
112
|
assert legacy not in ensure_sig.parameters
|
|
94
113
|
|
|
95
114
|
for name in ("shared_app_slug", "target_app_slug", "target_env", "bootstrap_profile_ref"):
|
|
@@ -135,7 +154,7 @@ async def test_authority_scope_methods_use_canonical_context_wire_names():
|
|
|
135
154
|
namespace = AuthorityNamespace(client)
|
|
136
155
|
|
|
137
156
|
await namespace.ensure_run(
|
|
138
|
-
|
|
157
|
+
workflow_recipe_ref="recipe://workflow-recipe-v1/report-cycle@head",
|
|
139
158
|
app_slug="carebridge",
|
|
140
159
|
env="production",
|
|
141
160
|
target_org_id="org-123",
|
|
@@ -255,7 +274,10 @@ async def test_authority_scope_methods_use_canonical_context_wire_names():
|
|
|
255
274
|
assert ensure_body["env"] == "production"
|
|
256
275
|
assert ensure_body["target_org_id"] == "org-123"
|
|
257
276
|
assert ensure_body["target_env"] == "production"
|
|
277
|
+
assert ensure_body["workflow_recipe_ref"] == "recipe://workflow-recipe-v1/report-cycle@head"
|
|
258
278
|
assert "bootstrap_profile_ref" not in ensure_body
|
|
279
|
+
assert "workflow_id" not in ensure_body
|
|
280
|
+
assert "workflow_ref" not in ensure_body
|
|
259
281
|
|
|
260
282
|
bootstrap_body = client.calls[1]["body"]
|
|
261
283
|
assert bootstrap_body["shared_app_slug"] == "shared-core"
|
|
@@ -478,6 +500,24 @@ async def test_authority_ensure_run_provisioning_bootstrap_omits_workflow_mode()
|
|
|
478
500
|
assert "mode" not in body
|
|
479
501
|
|
|
480
502
|
|
|
503
|
+
@pytest.mark.asyncio
|
|
504
|
+
async def test_authority_ensure_run_requires_recipe_launch_ref():
|
|
505
|
+
client = FakeClient()
|
|
506
|
+
namespace = AuthorityNamespace(client)
|
|
507
|
+
|
|
508
|
+
with pytest.raises(ValueError, match="workflow_recipe_ref or pipeline_recipe_ref"):
|
|
509
|
+
await namespace.ensure_run(app_slug="carebridge", env="production")
|
|
510
|
+
|
|
511
|
+
with pytest.raises(ValueError, match="recipe://workflow-recipe-v1/"):
|
|
512
|
+
await namespace.ensure_run(
|
|
513
|
+
workflow_recipe_ref="wf://carebridge/report-cycle",
|
|
514
|
+
app_slug="carebridge",
|
|
515
|
+
env="production",
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
assert client.calls == []
|
|
519
|
+
|
|
520
|
+
|
|
481
521
|
@pytest.mark.asyncio
|
|
482
522
|
async def test_authority_ensure_run_bootstrap_requires_provisioning_target_slug():
|
|
483
523
|
client = FakeClient()
|
|
@@ -47,3 +47,39 @@ async def test_recipes_namespace_maps_deprecate_to_gateway_route(monkeypatch, sd
|
|
|
47
47
|
"timeout": 30.0,
|
|
48
48
|
}
|
|
49
49
|
]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@pytest.mark.asyncio
|
|
53
|
+
async def test_recipes_namespace_passes_actor_context_on_publish(monkeypatch, sdk):
|
|
54
|
+
calls = []
|
|
55
|
+
|
|
56
|
+
async def fake_request(**kwargs):
|
|
57
|
+
calls.append(kwargs)
|
|
58
|
+
return {"ok": True, "recipe": {"version": 1}}
|
|
59
|
+
|
|
60
|
+
monkeypatch.setattr(sdk, "_request", fake_request)
|
|
61
|
+
|
|
62
|
+
actor = {"type": "user", "id": "user-1"}
|
|
63
|
+
await sdk.recipes.publish(
|
|
64
|
+
type="browser-recipe",
|
|
65
|
+
tier="project",
|
|
66
|
+
name="cms-login",
|
|
67
|
+
body="version: browser-recipe-v1\nsteps: []\n",
|
|
68
|
+
actor_context=actor,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
assert calls == [
|
|
72
|
+
{
|
|
73
|
+
"endpoint": "/api/recipe/recipes/publish",
|
|
74
|
+
"method": "POST",
|
|
75
|
+
"body": {
|
|
76
|
+
"type": "browser-recipe",
|
|
77
|
+
"tier": "project",
|
|
78
|
+
"name": "cms-login",
|
|
79
|
+
"body": "version: browser-recipe-v1\nsteps: []\n",
|
|
80
|
+
},
|
|
81
|
+
"use_gateway": True,
|
|
82
|
+
"timeout": 30.0,
|
|
83
|
+
"actor": actor,
|
|
84
|
+
}
|
|
85
|
+
]
|
|
@@ -255,7 +255,7 @@ async def test_workflow_ensure_supports_authority_one_call_lifecycle():
|
|
|
255
255
|
namespace = WorkflowNamespace(client)
|
|
256
256
|
|
|
257
257
|
await namespace.ensure(
|
|
258
|
-
"
|
|
258
|
+
workflow_recipe_ref="recipe://workflow-recipe-v1/report-cycle@head",
|
|
259
259
|
subject="PCM47474562",
|
|
260
260
|
company="summit-radiology",
|
|
261
261
|
inputs={"report_snapshot": "ar://artifact"},
|
|
@@ -265,7 +265,7 @@ async def test_workflow_ensure_supports_authority_one_call_lifecycle():
|
|
|
265
265
|
|
|
266
266
|
assert client.calls[0]["endpoint"] == "/api/authority/runs/ensure"
|
|
267
267
|
body = client.calls[0]["body"]
|
|
268
|
-
assert body["
|
|
268
|
+
assert body["workflow_recipe_ref"] == "recipe://workflow-recipe-v1/report-cycle@head"
|
|
269
269
|
assert body["subject"] == "PCM47474562"
|
|
270
270
|
assert body["company"] == "summit-radiology"
|
|
271
271
|
assert body["inputs"] == {"report_snapshot": "ar://artifact"}
|
|
@@ -274,6 +274,23 @@ async def test_workflow_ensure_supports_authority_one_call_lifecycle():
|
|
|
274
274
|
assert isinstance(body["idempotency_key"], str) and body["idempotency_key"]
|
|
275
275
|
|
|
276
276
|
|
|
277
|
+
@pytest.mark.asyncio
|
|
278
|
+
async def test_workflow_ensure_can_infer_recipe_ref_from_first_argument():
|
|
279
|
+
client = FakeClient()
|
|
280
|
+
namespace = WorkflowNamespace(client)
|
|
281
|
+
|
|
282
|
+
await namespace.ensure(
|
|
283
|
+
"recipe://workflow-recipe-v1/report-cycle@v3",
|
|
284
|
+
company="summit-radiology",
|
|
285
|
+
mode="async",
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
body = client.calls[0]["body"]
|
|
289
|
+
assert body["workflow_recipe_ref"] == "recipe://workflow-recipe-v1/report-cycle@v3"
|
|
290
|
+
assert body["company"] == "summit-radiology"
|
|
291
|
+
assert "workflow_id" not in body
|
|
292
|
+
|
|
293
|
+
|
|
277
294
|
@pytest.mark.asyncio
|
|
278
295
|
async def test_workflow_get_detects_authority_run_ids():
|
|
279
296
|
client = FakeClient()
|
|
@@ -416,24 +433,24 @@ async def test_saved_workflow_instance_ensure_contract_matches_workflow_manager(
|
|
|
416
433
|
|
|
417
434
|
|
|
418
435
|
@pytest.mark.asyncio
|
|
419
|
-
async def
|
|
436
|
+
async def test_recipe_workflow_ensure_is_authority_only_and_stream_guard():
|
|
420
437
|
client = FakeClient()
|
|
421
438
|
namespace = WorkflowNamespace(client)
|
|
422
439
|
|
|
423
440
|
await namespace.ensure(
|
|
424
|
-
"
|
|
441
|
+
workflow_recipe_ref="recipe://workflow-recipe-v1/report-cycle@head",
|
|
425
442
|
mode="ensure_only",
|
|
426
443
|
)
|
|
427
444
|
|
|
428
445
|
call = client.calls[0]
|
|
429
446
|
assert call["endpoint"] == "/api/authority/runs/ensure"
|
|
430
|
-
assert call["body"]["
|
|
447
|
+
assert call["body"]["workflow_recipe_ref"] == "recipe://workflow-recipe-v1/report-cycle@head"
|
|
431
448
|
assert call["body"]["mode"] == "ensure_only"
|
|
432
449
|
assert isinstance(call["body"]["idempotency_key"], str)
|
|
433
450
|
|
|
434
451
|
with pytest.raises(ValueError, match="Authority-only and supports blocking, async, or ensure_only"):
|
|
435
452
|
await namespace.ensure(
|
|
436
|
-
"
|
|
453
|
+
workflow_recipe_ref="recipe://workflow-recipe-v1/report-cycle@head",
|
|
437
454
|
mode="streaming",
|
|
438
455
|
)
|
|
439
456
|
|
|
@@ -56,24 +56,62 @@ async def test_workflow_get_accepts_workflow_ref():
|
|
|
56
56
|
|
|
57
57
|
|
|
58
58
|
@pytest.mark.asyncio
|
|
59
|
-
async def
|
|
59
|
+
async def test_workflow_ensure_rejects_legacy_workflow_ref():
|
|
60
|
+
client = FakeClient()
|
|
61
|
+
namespace = WorkflowNamespace(client)
|
|
62
|
+
|
|
63
|
+
with pytest.raises(ValueError, match="recipe_ref"):
|
|
64
|
+
await namespace.ensure(
|
|
65
|
+
"wf://carebridge-platform/production/shared/patient-intake",
|
|
66
|
+
subject="sub-1",
|
|
67
|
+
company="co-1",
|
|
68
|
+
instance_id="run-123",
|
|
69
|
+
context={"report_ref": {"report_id": "r-1"}},
|
|
70
|
+
mode="async",
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
assert client.calls == []
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@pytest.mark.asyncio
|
|
77
|
+
async def test_workflow_ensure_accepts_recipe_refs():
|
|
60
78
|
client = FakeClient()
|
|
61
79
|
namespace = WorkflowNamespace(client)
|
|
62
80
|
|
|
63
81
|
await namespace.ensure(
|
|
64
|
-
"
|
|
82
|
+
workflow_recipe_ref="recipe://workflow-recipe-v1/patient-intake@v4",
|
|
65
83
|
subject="sub-1",
|
|
66
84
|
company="co-1",
|
|
67
|
-
|
|
85
|
+
mode="async",
|
|
86
|
+
)
|
|
87
|
+
await namespace.ensure(
|
|
88
|
+
pipeline_recipe_ref="recipe://pipeline-recipe-v1/intake-pipeline@v2",
|
|
68
89
|
context={"report_ref": {"report_id": "r-1"}},
|
|
69
90
|
mode="async",
|
|
70
91
|
)
|
|
71
92
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
assert
|
|
75
|
-
assert
|
|
76
|
-
|
|
93
|
+
first = client.calls[0]["body"]
|
|
94
|
+
assert first["workflow_recipe_ref"] == "recipe://workflow-recipe-v1/patient-intake@v4"
|
|
95
|
+
assert first["subject"] == "sub-1"
|
|
96
|
+
assert "workflow_id" not in first
|
|
97
|
+
second = client.calls[1]["body"]
|
|
98
|
+
assert second["pipeline_recipe_ref"] == "recipe://pipeline-recipe-v1/intake-pipeline@v2"
|
|
99
|
+
assert second["context"] == {"report_ref": {"report_id": "r-1"}}
|
|
100
|
+
assert "workflow_ref" not in second
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@pytest.mark.asyncio
|
|
104
|
+
async def test_workflow_ensure_rejects_mixed_recipe_refs():
|
|
105
|
+
client = FakeClient()
|
|
106
|
+
namespace = WorkflowNamespace(client)
|
|
107
|
+
|
|
108
|
+
with pytest.raises(ValueError, match="exactly one"):
|
|
109
|
+
await namespace.ensure(
|
|
110
|
+
"recipe://pipeline-recipe-v1/intake-pipeline@v2",
|
|
111
|
+
workflow_recipe_ref="recipe://workflow-recipe-v1/patient-intake@v4",
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
assert client.calls == []
|
|
77
115
|
|
|
78
116
|
|
|
79
117
|
@pytest.mark.asyncio
|
|
@@ -125,7 +163,7 @@ def test_artifact_ref_helpers_round_trip():
|
|
|
125
163
|
}
|
|
126
164
|
|
|
127
165
|
|
|
128
|
-
def
|
|
166
|
+
def test_artifact_ref_helpers_support_v2_refs():
|
|
129
167
|
v2_ref = build_artifact_ref(
|
|
130
168
|
group="carebridge",
|
|
131
169
|
owner="carebridge-summit",
|
|
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-5.0.0 → dominus_sdk_python-6.0.0}/dominus_sdk_python.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/dominus_sdk_python.egg-info/requires.txt
RENAMED
|
File without changes
|
{dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/dominus_sdk_python.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dominus_sdk_python-5.0.0 → dominus_sdk_python-6.0.0}/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
|