dominus-sdk-python 5.1.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.
Files changed (63) hide show
  1. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/PKG-INFO +3 -9
  2. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/README.md +2 -8
  3. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/dominus/__init__.py +1 -1
  4. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/authority.py +33 -12
  5. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/recipes.py +24 -8
  6. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/workflow.py +26 -35
  7. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/dominus_sdk_python.egg-info/PKG-INFO +3 -9
  8. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/pyproject.toml +1 -1
  9. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/tests/test_authority_public_vocabulary.py +43 -3
  10. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/tests/test_recipes_namespace.py +36 -0
  11. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/tests/test_workflow_lifecycle.py +6 -6
  12. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/tests/test_workflow_refs.py +13 -16
  13. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/dominus/config/__init__.py +0 -0
  14. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/dominus/config/endpoints.py +0 -0
  15. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/dominus/errors.py +0 -0
  16. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/dominus/helpers/__init__.py +0 -0
  17. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/dominus/helpers/auth.py +0 -0
  18. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/dominus/helpers/cache.py +0 -0
  19. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/dominus/helpers/console_capture.py +0 -0
  20. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/dominus/helpers/core.py +0 -0
  21. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/dominus/helpers/crypto.py +0 -0
  22. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/dominus/helpers/sse.py +0 -0
  23. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/dominus/helpers/trace.py +0 -0
  24. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/__init__.py +0 -0
  25. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/admin.py +0 -0
  26. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/ai.py +0 -0
  27. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/artifacts.py +0 -0
  28. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/auth.py +0 -0
  29. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/browser.py +0 -0
  30. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/courier.py +0 -0
  31. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/db.py +0 -0
  32. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/ddl.py +0 -0
  33. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/deployer.py +0 -0
  34. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/fastapi.py +0 -0
  35. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/files.py +0 -0
  36. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/health.py +0 -0
  37. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/jobs.py +0 -0
  38. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/logs.py +0 -0
  39. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/portal.py +0 -0
  40. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/processor.py +0 -0
  41. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/redis.py +0 -0
  42. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/secrets.py +0 -0
  43. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/secure.py +0 -0
  44. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/stash.py +0 -0
  45. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/sync.py +0 -0
  46. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/dominus/namespaces/warden.py +0 -0
  47. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/dominus/services/__init__.py +0 -0
  48. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/dominus/start.py +0 -0
  49. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/dominus_sdk_python.egg-info/SOURCES.txt +0 -0
  50. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/dominus_sdk_python.egg-info/dependency_links.txt +0 -0
  51. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/dominus_sdk_python.egg-info/requires.txt +0 -0
  52. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/dominus_sdk_python.egg-info/top_level.txt +0 -0
  53. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/setup.cfg +0 -0
  54. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/tests/test_auth.py +0 -0
  55. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/tests/test_browser_namespace.py +0 -0
  56. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/tests/test_control_plane_namespaces.py +0 -0
  57. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/tests/test_errors.py +0 -0
  58. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/tests/test_flat_commands.py +0 -0
  59. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/tests/test_health.py +0 -0
  60. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/tests/test_logs.py +0 -0
  61. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/tests/test_provisioning_parity.py +0 -0
  62. {dominus_sdk_python-5.1.0 → dominus_sdk_python-6.0.0}/tests/test_public_exports.py +0 -0
  63. {dominus_sdk_python-5.1.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: 5.1.0
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: `5.1.0`
46
+ - Current package version: `6.0.0`
47
47
 
48
48
  ## Install
49
49
 
@@ -66,12 +66,6 @@ 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
- "wf://carebridge/report-cycle",
70
- subject="PCM47474562",
71
- company="summit-radiology",
72
- )
73
-
74
- recipe_run = await dominus.workflow.ensure(
75
69
  workflow_recipe_ref="recipe://workflow-recipe-v1/report-cycle@v3",
76
70
  subject="PCM47474562",
77
71
  company="summit-radiology",
@@ -199,7 +193,7 @@ JWT and selected scope headers directly through Gateway.
199
193
  | `health` | Gateway | Health and ping helpers |
200
194
  | `admin` | Admin Worker | Admin category reseed/reset |
201
195
  | `ai` | Agent Runtime | Agent, completion, RAG, artifacts, results, raw orchestration |
202
- | `workflow` | Workflow Manager + Authority | Saved workflow CRUD and canonical run lifecycle |
196
+ | `workflow` | Workflow Manager + Authority | Saved workflow CRUD and recipe-backed run lifecycle |
203
197
  | `artifacts` | Artifact Worker | Addressed V2 artifacts, bookmarks, watches |
204
198
  | `jobs` | Job Worker | Enqueue, poll, dead-letter management |
205
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: `5.1.0`
12
+ - Current package version: `6.0.0`
13
13
 
14
14
  ## Install
15
15
 
@@ -32,12 +32,6 @@ 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
- "wf://carebridge/report-cycle",
36
- subject="PCM47474562",
37
- company="summit-radiology",
38
- )
39
-
40
- recipe_run = await dominus.workflow.ensure(
41
35
  workflow_recipe_ref="recipe://workflow-recipe-v1/report-cycle@v3",
42
36
  subject="PCM47474562",
43
37
  company="summit-radiology",
@@ -165,7 +159,7 @@ JWT and selected scope headers directly through Gateway.
165
159
  | `health` | Gateway | Health and ping helpers |
166
160
  | `admin` | Admin Worker | Admin category reseed/reset |
167
161
  | `ai` | Agent Runtime | Agent, completion, RAG, artifacts, results, raw orchestration |
168
- | `workflow` | Workflow Manager + Authority | Saved workflow CRUD and canonical run lifecycle |
162
+ | `workflow` | Workflow Manager + Authority | Saved workflow CRUD and recipe-backed run lifecycle |
169
163
  | `artifacts` | Artifact Worker | Addressed V2 artifacts, bookmarks, watches |
170
164
  | `jobs` | Job Worker | Enqueue, poll, dead-letter management |
171
165
  | `processor` | Processor | Batch and single-job processing |
@@ -162,7 +162,7 @@ from .errors import (
162
162
  TimeoutError as DominusTimeoutError,
163
163
  )
164
164
 
165
- __version__ = "5.1.0"
165
+ __version__ = "6.0.0"
166
166
  __all__ = [
167
167
  # Main SDK instance
168
168
  "dominus",
@@ -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 "launch a saved workflow" path; use ``dominus.authority.ensure_run``
16
- when you want to think in Authority terms (run lifecycle, run dossiers,
17
- retries, cancels).
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(workflow_id="...", subject="PCM47474562")
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
- workflow_id: Optional[str] = None,
171
- workflow_ref: Optional[str] = None,
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
- if not bootstrap and not workflow_id and not workflow_ref:
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 workflow_id or workflow_ref unless run_kind is "
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
- "workflow_id": workflow_id,
266
- "workflow_ref": workflow_ref,
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(self, endpoint: str, body: Optional[Dict[str, Any]] = None, *, timeout: float = 30.0) -> Dict[str, Any]:
45
- return await self._client._request(
46
- endpoint=endpoint,
47
- method="POST",
48
- body=body or {},
49
- use_gateway=True,
50
- timeout=timeout,
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 saved-workflow launch (Authority POST /api/authority/runs/ensure)
36
+ # Canonical recipe-backed launch (Authority POST /api/authority/runs/ensure)
37
37
  execution = await dominus.workflow.ensure(
38
- "my-workflow-id",
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 saved-workflow run lifecycle via the dominus-workflow-manager service.
61
+ and the recipe-backed run lifecycle via Authority.
62
62
  """
63
63
 
64
64
  def __init__(self, client: "Dominus"):
@@ -91,19 +91,6 @@ class WorkflowNamespace:
91
91
  endpoint += "?include_content=true"
92
92
  return endpoint
93
93
 
94
- @staticmethod
95
- def _workflow_identifier_body(
96
- workflow_id: Optional[str] = None,
97
- workflow_ref: Optional[str] = None,
98
- ) -> Dict[str, Any]:
99
- if workflow_ref and str(workflow_ref).strip():
100
- return {"workflow_ref": str(workflow_ref).strip()}
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")
106
-
107
94
  @staticmethod
108
95
  def _is_workflow_recipe_ref(value: Optional[str]) -> bool:
109
96
  return str(value or "").strip().startswith("recipe://workflow-recipe-v1/")
@@ -218,13 +205,14 @@ class WorkflowNamespace:
218
205
 
219
206
  def _build_authority_ensure_body(
220
207
  self,
221
- workflow_id: Optional[str] = None,
208
+ recipe_ref: Optional[str] = None,
222
209
  *,
223
- workflow_ref: Optional[str] = None,
224
210
  workflow_recipe_ref: Optional[str] = None,
225
211
  pipeline_recipe_ref: Optional[str] = None,
226
212
  subject: Optional[str] = None,
227
213
  company: Optional[str] = None,
214
+ group: Optional[str] = None,
215
+ owner: Optional[str] = None,
228
216
  inputs: Optional[Dict[str, Any]] = None,
229
217
  context: Optional[Dict[str, Any]] = None,
230
218
  bindings: Optional[Dict[str, Any]] = None,
@@ -238,26 +226,25 @@ class WorkflowNamespace:
238
226
  ) -> Dict[str, Any]:
239
227
  body: Dict[str, Any] = {"mode": mode}
240
228
  launch_sources: List[tuple[str, str]] = []
241
- normalized_workflow_id = str(workflow_id or "").strip()
242
- if workflow_ref and str(workflow_ref).strip():
243
- launch_sources.append(("workflow_ref", str(workflow_ref).strip()))
229
+ normalized_recipe_ref = str(recipe_ref or "").strip()
244
230
  if workflow_recipe_ref and str(workflow_recipe_ref).strip():
245
231
  launch_sources.append(("workflow_recipe_ref", str(workflow_recipe_ref).strip()))
246
232
  if pipeline_recipe_ref and str(pipeline_recipe_ref).strip():
247
233
  launch_sources.append(("pipeline_recipe_ref", str(pipeline_recipe_ref).strip()))
248
- if normalized_workflow_id:
249
- if self._is_workflow_ref(normalized_workflow_id):
250
- launch_sources.append(("workflow_ref", normalized_workflow_id))
251
- elif self._is_workflow_recipe_ref(normalized_workflow_id):
252
- launch_sources.append(("workflow_recipe_ref", normalized_workflow_id))
253
- elif self._is_pipeline_recipe_ref(normalized_workflow_id):
254
- launch_sources.append(("pipeline_recipe_ref", normalized_workflow_id))
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))
255
239
  else:
256
- launch_sources.append(("workflow_id", normalized_workflow_id))
240
+ raise ValueError(
241
+ "recipe_ref must start with recipe://workflow-recipe-v1/ "
242
+ "or recipe://pipeline-recipe-v1/"
243
+ )
257
244
  if len(launch_sources) != 1:
258
245
  raise ValueError(
259
- "ensure requires exactly one of workflow_id, workflow_ref, "
260
- "workflow_recipe_ref, or pipeline_recipe_ref"
246
+ "ensure requires exactly one of recipe_ref, workflow_recipe_ref, "
247
+ "or pipeline_recipe_ref"
261
248
  )
262
249
  field, value = launch_sources[0]
263
250
  body[field] = value
@@ -265,6 +252,10 @@ class WorkflowNamespace:
265
252
  body["subject"] = subject
266
253
  if company is not None:
267
254
  body["company"] = company
255
+ if group is not None:
256
+ body["group"] = group
257
+ if owner is not None:
258
+ body["owner"] = owner
268
259
  if inputs:
269
260
  body["inputs"] = inputs
270
261
  if context:
@@ -883,7 +874,7 @@ class WorkflowNamespace:
883
874
 
884
875
  async def ensure(
885
876
  self,
886
- workflow_id: Optional[str] = None,
877
+ recipe_ref: Optional[str] = None,
887
878
  *,
888
879
  instance_id: Optional[str] = None,
889
880
  group: Optional[str] = None,
@@ -893,7 +884,6 @@ class WorkflowNamespace:
893
884
  bindings: Optional[Dict[str, Any]] = None,
894
885
  mode: str = "blocking",
895
886
  context: Optional[Dict[str, Any]] = None,
896
- workflow_ref: Optional[str] = None,
897
887
  workflow_recipe_ref: Optional[str] = None,
898
888
  pipeline_recipe_ref: Optional[str] = None,
899
889
  subject: Optional[str] = None,
@@ -914,12 +904,13 @@ class WorkflowNamespace:
914
904
  return await self._api(
915
905
  endpoint="/api/authority/runs/ensure",
916
906
  body=self._build_authority_ensure_body(
917
- workflow_id,
918
- workflow_ref=workflow_ref,
907
+ recipe_ref,
919
908
  workflow_recipe_ref=workflow_recipe_ref,
920
909
  pipeline_recipe_ref=pipeline_recipe_ref,
921
910
  subject=subject,
922
911
  company=company,
912
+ group=group,
913
+ owner=owner,
923
914
  inputs=inputs,
924
915
  context=context,
925
916
  bindings=bindings,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dominus-sdk-python
3
- Version: 5.1.0
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: `5.1.0`
46
+ - Current package version: `6.0.0`
47
47
 
48
48
  ## Install
49
49
 
@@ -66,12 +66,6 @@ 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
- "wf://carebridge/report-cycle",
70
- subject="PCM47474562",
71
- company="summit-radiology",
72
- )
73
-
74
- recipe_run = await dominus.workflow.ensure(
75
69
  workflow_recipe_ref="recipe://workflow-recipe-v1/report-cycle@v3",
76
70
  subject="PCM47474562",
77
71
  company="summit-radiology",
@@ -199,7 +193,7 @@ JWT and selected scope headers directly through Gateway.
199
193
  | `health` | Gateway | Health and ping helpers |
200
194
  | `admin` | Admin Worker | Admin category reseed/reset |
201
195
  | `ai` | Agent Runtime | Agent, completion, RAG, artifacts, results, raw orchestration |
202
- | `workflow` | Workflow Manager + Authority | Saved workflow CRUD and canonical run lifecycle |
196
+ | `workflow` | Workflow Manager + Authority | Saved workflow CRUD and recipe-backed run lifecycle |
203
197
  | `artifacts` | Artifact Worker | Addressed V2 artifacts, bookmarks, watches |
204
198
  | `jobs` | Job Worker | Enqueue, poll, dead-letter management |
205
199
  | `processor` | Processor | Batch and single-job processing |
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "dominus-sdk-python"
7
- version = "5.1.0"
7
+ version = "6.0.0"
8
8
  description = "Python SDK for the Dominus gateway-first platform"
9
9
  readme = "README.md"
10
10
  license = {text = "Proprietary"}
@@ -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 ("app_slug", "env", "target_org_id", "target_env", "run_kind", "target_app_slug", "bootstrap_profile_ref", "provisioning_target_slug"):
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 ("project_slug", "environment", "target_project_id", "target_environment", old_profile_key):
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
- workflow_id="wf-123",
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
- "wf://carebridge/report-cycle",
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["workflow_ref"] == "wf://carebridge/report-cycle"
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"}
@@ -433,24 +433,24 @@ async def test_saved_workflow_instance_ensure_contract_matches_workflow_manager(
433
433
 
434
434
 
435
435
  @pytest.mark.asyncio
436
- async def test_saved_workflow_ensure_is_authority_only_and_stream_guard():
436
+ async def test_recipe_workflow_ensure_is_authority_only_and_stream_guard():
437
437
  client = FakeClient()
438
438
  namespace = WorkflowNamespace(client)
439
439
 
440
440
  await namespace.ensure(
441
- "wf-instance",
441
+ workflow_recipe_ref="recipe://workflow-recipe-v1/report-cycle@head",
442
442
  mode="ensure_only",
443
443
  )
444
444
 
445
445
  call = client.calls[0]
446
446
  assert call["endpoint"] == "/api/authority/runs/ensure"
447
- assert call["body"]["workflow_id"] == "wf-instance"
447
+ assert call["body"]["workflow_recipe_ref"] == "recipe://workflow-recipe-v1/report-cycle@head"
448
448
  assert call["body"]["mode"] == "ensure_only"
449
449
  assert isinstance(call["body"]["idempotency_key"], str)
450
450
 
451
451
  with pytest.raises(ValueError, match="Authority-only and supports blocking, async, or ensure_only"):
452
452
  await namespace.ensure(
453
- "wf-instance",
453
+ workflow_recipe_ref="recipe://workflow-recipe-v1/report-cycle@head",
454
454
  mode="streaming",
455
455
  )
456
456
 
@@ -56,24 +56,21 @@ async def test_workflow_get_accepts_workflow_ref():
56
56
 
57
57
 
58
58
  @pytest.mark.asyncio
59
- async def test_workflow_ensure_accepts_workflow_ref():
59
+ async def test_workflow_ensure_rejects_legacy_workflow_ref():
60
60
  client = FakeClient()
61
61
  namespace = WorkflowNamespace(client)
62
62
 
63
- await namespace.ensure(
64
- "wf://carebridge-platform/production/shared/patient-intake",
65
- subject="sub-1",
66
- company="co-1",
67
- instance_id="run-123",
68
- context={"report_ref": {"report_id": "r-1"}},
69
- mode="async",
70
- )
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
+ )
71
72
 
72
- assert client.calls[0]["endpoint"] == "/api/authority/runs/ensure"
73
- body = client.calls[0]["body"]
74
- assert body["workflow_ref"] == "wf://carebridge-platform/production/shared/patient-intake"
75
- assert body["instance_id"] == "run-123"
76
- assert body["context"] == {"report_ref": {"report_id": "r-1"}}
73
+ assert client.calls == []
77
74
 
78
75
 
79
76
  @pytest.mark.asyncio
@@ -104,13 +101,13 @@ async def test_workflow_ensure_accepts_recipe_refs():
104
101
 
105
102
 
106
103
  @pytest.mark.asyncio
107
- async def test_workflow_ensure_rejects_mixed_legacy_and_recipe_refs():
104
+ async def test_workflow_ensure_rejects_mixed_recipe_refs():
108
105
  client = FakeClient()
109
106
  namespace = WorkflowNamespace(client)
110
107
 
111
108
  with pytest.raises(ValueError, match="exactly one"):
112
109
  await namespace.ensure(
113
- "wf-123",
110
+ "recipe://pipeline-recipe-v1/intake-pipeline@v2",
114
111
  workflow_recipe_ref="recipe://workflow-recipe-v1/patient-intake@v4",
115
112
  )
116
113