dominus-sdk-python 4.0.7__tar.gz → 4.1.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 (60) hide show
  1. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/PKG-INFO +31 -2
  2. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/README.md +30 -1
  3. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/dominus/__init__.py +3 -1
  4. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/dominus/namespaces/__init__.py +2 -0
  5. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/dominus/namespaces/authority.py +54 -0
  6. dominus_sdk_python-4.1.0/dominus/namespaces/browser.py +198 -0
  7. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/dominus/start.py +4 -0
  8. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/dominus_sdk_python.egg-info/PKG-INFO +31 -2
  9. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/dominus_sdk_python.egg-info/SOURCES.txt +2 -0
  10. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/pyproject.toml +1 -1
  11. dominus_sdk_python-4.1.0/tests/test_browser_namespace.py +107 -0
  12. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/tests/test_public_exports.py +8 -0
  13. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/dominus/config/__init__.py +0 -0
  14. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/dominus/config/endpoints.py +0 -0
  15. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/dominus/errors.py +0 -0
  16. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/dominus/helpers/__init__.py +0 -0
  17. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/dominus/helpers/auth.py +0 -0
  18. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/dominus/helpers/cache.py +0 -0
  19. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/dominus/helpers/console_capture.py +0 -0
  20. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/dominus/helpers/core.py +0 -0
  21. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/dominus/helpers/crypto.py +0 -0
  22. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/dominus/helpers/sse.py +0 -0
  23. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/dominus/helpers/trace.py +0 -0
  24. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/dominus/namespaces/admin.py +0 -0
  25. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/dominus/namespaces/ai.py +0 -0
  26. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/dominus/namespaces/artifacts.py +0 -0
  27. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/dominus/namespaces/auth.py +0 -0
  28. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/dominus/namespaces/courier.py +0 -0
  29. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/dominus/namespaces/db.py +0 -0
  30. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/dominus/namespaces/ddl.py +0 -0
  31. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/dominus/namespaces/deployer.py +0 -0
  32. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/dominus/namespaces/fastapi.py +0 -0
  33. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/dominus/namespaces/files.py +0 -0
  34. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/dominus/namespaces/health.py +0 -0
  35. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/dominus/namespaces/jobs.py +0 -0
  36. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/dominus/namespaces/logs.py +0 -0
  37. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/dominus/namespaces/portal.py +0 -0
  38. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/dominus/namespaces/processor.py +0 -0
  39. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/dominus/namespaces/redis.py +0 -0
  40. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/dominus/namespaces/secrets.py +0 -0
  41. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/dominus/namespaces/secure.py +0 -0
  42. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/dominus/namespaces/sync.py +0 -0
  43. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/dominus/namespaces/warden.py +0 -0
  44. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/dominus/namespaces/workflow.py +0 -0
  45. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/dominus/services/__init__.py +0 -0
  46. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/dominus_sdk_python.egg-info/dependency_links.txt +0 -0
  47. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/dominus_sdk_python.egg-info/requires.txt +0 -0
  48. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/dominus_sdk_python.egg-info/top_level.txt +0 -0
  49. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/setup.cfg +0 -0
  50. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/tests/test_auth.py +0 -0
  51. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/tests/test_authority_public_vocabulary.py +0 -0
  52. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/tests/test_control_plane_namespaces.py +0 -0
  53. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/tests/test_errors.py +0 -0
  54. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/tests/test_flat_commands.py +0 -0
  55. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/tests/test_health.py +0 -0
  56. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/tests/test_logs.py +0 -0
  57. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/tests/test_provisioning_parity.py +0 -0
  58. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/tests/test_transport_compat.py +0 -0
  59. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.0}/tests/test_workflow_lifecycle.py +0 -0
  60. {dominus_sdk_python-4.0.7 → dominus_sdk_python-4.1.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: 4.0.7
3
+ Version: 4.1.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
@@ -42,7 +42,7 @@ Async Python SDK for the Dominus gateway-first service plane.
42
42
  - Gateway-scoped client mode for MCP and other user-JWT sessions
43
43
  - Transport compatibility for wrapped `{success,data}` responses and unwrapped Warden/control-plane success objects
44
44
  - Local helpers for JWT verification, trace propagation, retries, and console capture
45
- - Current package version: `4.0.7`
45
+ - Current package version: `4.0.8`
46
46
 
47
47
  ## Install
48
48
 
@@ -115,6 +115,34 @@ buffer_cleanup = await dominus.logs.run_archive_buffer_maintenance(
115
115
  schedules = await dominus.authority.list_schedules(all_scopes=True)
116
116
  ```
117
117
 
118
+ ## Browser Automation
119
+
120
+ `dominus.browser` exposes the first-class Dominus browser automation primitive through authenticated gateway routes under `/svc/browser/*`. SDK methods use `/api/browser/*` internally with gateway routing enabled; worker-local routes remain `/health` and `/runs/*`.
121
+
122
+ ```python
123
+ health = await dominus.browser.get_health()
124
+ run = await dominus.browser.ensure_run(
125
+ idempotency_key="route-check-1",
126
+ target={"url": "https://example.com/dashboard"},
127
+ provider="auto",
128
+ mode="playwright",
129
+ capture_policy={
130
+ "screenshots": "never",
131
+ "trace": "never",
132
+ "har": "never",
133
+ "video": "never",
134
+ "dom_snapshot": "never",
135
+ "raw_response_bodies": "never",
136
+ "phi_risk": "possible",
137
+ },
138
+ assertions=[{"kind": "status_code", "expected": 200}],
139
+ )
140
+ await dominus.browser.start_run(run["run_id"])
141
+ status = await dominus.browser.get_run_status(run["run_id"])
142
+ ```
143
+
144
+ Cloudflare Browser Run is the default provider. Browserbase is the fallback for future persistent authenticated/HITL work. Browser run metadata is runtime state owned by the browser worker; Artifact V2 is only for sanitized result/capture payloads.
145
+
118
146
  ## Session-Scoped Clients
119
147
 
120
148
  Production MCP and other user-session callers should instantiate `Dominus` with
@@ -169,6 +197,7 @@ JWT and selected scope headers directly through Gateway.
169
197
  | `processor` | Processor | Batch and single-job processing |
170
198
  | `sync` | Sync Worker | KV synchronization |
171
199
  | `authority` | Dominus Authority | Runs, companies, deploys, managed clients, context |
200
+ | `browser` | Browser Worker | Browser run health, ensure/start/status/result/retry/nudge/cancel/timeline/dossier |
172
201
  | `deployer` | Deployer | Thin operator control-plane request surface |
173
202
  | `warden` | Warden | Thin operator control-plane request surface |
174
203
  | `fastapi` | Local decorators | `@jwt`, `@psk`, `@scopes(...)` |
@@ -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: `4.0.7`
12
+ - Current package version: `4.0.8`
13
13
 
14
14
  ## Install
15
15
 
@@ -82,6 +82,34 @@ buffer_cleanup = await dominus.logs.run_archive_buffer_maintenance(
82
82
  schedules = await dominus.authority.list_schedules(all_scopes=True)
83
83
  ```
84
84
 
85
+ ## Browser Automation
86
+
87
+ `dominus.browser` exposes the first-class Dominus browser automation primitive through authenticated gateway routes under `/svc/browser/*`. SDK methods use `/api/browser/*` internally with gateway routing enabled; worker-local routes remain `/health` and `/runs/*`.
88
+
89
+ ```python
90
+ health = await dominus.browser.get_health()
91
+ run = await dominus.browser.ensure_run(
92
+ idempotency_key="route-check-1",
93
+ target={"url": "https://example.com/dashboard"},
94
+ provider="auto",
95
+ mode="playwright",
96
+ capture_policy={
97
+ "screenshots": "never",
98
+ "trace": "never",
99
+ "har": "never",
100
+ "video": "never",
101
+ "dom_snapshot": "never",
102
+ "raw_response_bodies": "never",
103
+ "phi_risk": "possible",
104
+ },
105
+ assertions=[{"kind": "status_code", "expected": 200}],
106
+ )
107
+ await dominus.browser.start_run(run["run_id"])
108
+ status = await dominus.browser.get_run_status(run["run_id"])
109
+ ```
110
+
111
+ Cloudflare Browser Run is the default provider. Browserbase is the fallback for future persistent authenticated/HITL work. Browser run metadata is runtime state owned by the browser worker; Artifact V2 is only for sanitized result/capture payloads.
112
+
85
113
  ## Session-Scoped Clients
86
114
 
87
115
  Production MCP and other user-session callers should instantiate `Dominus` with
@@ -136,6 +164,7 @@ JWT and selected scope headers directly through Gateway.
136
164
  | `processor` | Processor | Batch and single-job processing |
137
165
  | `sync` | Sync Worker | KV synchronization |
138
166
  | `authority` | Dominus Authority | Runs, companies, deploys, managed clients, context |
167
+ | `browser` | Browser Worker | Browser run health, ensure/start/status/result/retry/nudge/cancel/timeline/dossier |
139
168
  | `deployer` | Deployer | Thin operator control-plane request surface |
140
169
  | `warden` | Warden | Thin operator control-plane request surface |
141
170
  | `fastapi` | Local decorators | `@jwt`, `@psk`, `@scopes(...)` |
@@ -111,6 +111,7 @@ from .namespaces.jobs import JobsNamespace
111
111
  from .namespaces.processor import ProcessorNamespace
112
112
  from .namespaces.sync import SyncNamespace
113
113
  from .namespaces.authority import AuthorityNamespace
114
+ from .namespaces.browser import BrowserNamespace
114
115
  from .namespaces.deployer import DeployerNamespace
115
116
  from .namespaces.warden import WardenNamespace
116
117
 
@@ -163,7 +164,7 @@ from .errors import (
163
164
  TimeoutError as DominusTimeoutError,
164
165
  )
165
166
 
166
- __version__ = "4.0.7"
167
+ __version__ = "4.0.8"
167
168
  __all__ = [
168
169
  # Main SDK instance
169
170
  "dominus",
@@ -213,6 +214,7 @@ __all__ = [
213
214
  "ProcessorNamespace",
214
215
  "SyncNamespace",
215
216
  "AuthorityNamespace",
217
+ "BrowserNamespace",
216
218
  "DeployerNamespace",
217
219
  "WardenNamespace",
218
220
  # AI namespace for agent-runtime operations
@@ -15,6 +15,7 @@ from .jobs import JobsNamespace
15
15
  from .processor import ProcessorNamespace
16
16
  from .sync import SyncNamespace
17
17
  from .authority import AuthorityNamespace
18
+ from .browser import BrowserNamespace
18
19
  from .deployer import DeployerNamespace
19
20
  from .warden import WardenNamespace
20
21
  from .ai import (
@@ -42,6 +43,7 @@ __all__ = [
42
43
  "ProcessorNamespace",
43
44
  "SyncNamespace",
44
45
  "AuthorityNamespace",
46
+ "BrowserNamespace",
45
47
  "DeployerNamespace",
46
48
  "WardenNamespace",
47
49
  "AiNamespace",
@@ -1154,6 +1154,60 @@ class AuthorityNamespace:
1154
1154
  timeout=self._http_timeout(timeout, 30.0),
1155
1155
  )
1156
1156
 
1157
+ async def sync_deploy_verification(
1158
+ self,
1159
+ deploy_id: str,
1160
+ *,
1161
+ deployment_id: str,
1162
+ status: str,
1163
+ verification_status: str,
1164
+ verification_metadata: Optional[Dict[str, Any]] = None,
1165
+ rollout_id: Optional[str] = None,
1166
+ run_id: Optional[str] = None,
1167
+ repo_full_name: Optional[str] = None,
1168
+ branch: Optional[str] = None,
1169
+ sha: Optional[str] = None,
1170
+ service_name: Optional[str] = None,
1171
+ service_type: Optional[str] = None,
1172
+ build_id: Optional[str] = None,
1173
+ metadata: Optional[Dict[str, Any]] = None,
1174
+ app_slug: Optional[str] = None,
1175
+ env: Optional[str] = None,
1176
+ target_org_id: Optional[str] = None,
1177
+ target_env: Optional[str] = None,
1178
+ initiator_type: Optional[str] = None,
1179
+ initiator_id: Optional[str] = None,
1180
+ idempotency_key: Optional[str] = None,
1181
+ timeout: Optional[float] = None,
1182
+ ) -> Dict[str, Any]:
1183
+ """Sync post-deploy verification onto an Authority execution row."""
1184
+ if not deploy_id:
1185
+ raise ValueError("deploy_id is required")
1186
+ if not deployment_id:
1187
+ raise ValueError("deployment_id is required")
1188
+ body = _compact({
1189
+ "status": status,
1190
+ "deployment_id": deployment_id,
1191
+ "verification_status": verification_status,
1192
+ "verification_metadata": verification_metadata,
1193
+ "rollout_id": rollout_id,
1194
+ "run_id": run_id,
1195
+ "repo_full_name": repo_full_name,
1196
+ "branch": branch,
1197
+ "sha": sha,
1198
+ "service_name": service_name,
1199
+ "service_type": service_type,
1200
+ "build_id": build_id,
1201
+ "metadata": metadata,
1202
+ **self._initiator(initiator_type, initiator_id, idempotency_key),
1203
+ **self._scope(app_slug, env, target_org_id, target_env),
1204
+ })
1205
+ return await self._post(
1206
+ f"/api/authority/deploys/{quote(deploy_id, safe='')}/executions",
1207
+ body,
1208
+ timeout=self._http_timeout(timeout, 30.0),
1209
+ )
1210
+
1157
1211
  # ==================================================================
1158
1212
  # Managed clients
1159
1213
  # ==================================================================
@@ -0,0 +1,198 @@
1
+ """
2
+ Browser Namespace - first-class Dominus browser automation primitive.
3
+
4
+ All methods route through gateway (``/api/browser/*`` -> ``/svc/browser/*``).
5
+ Worker-local routes remain ``/health`` and ``/runs/*``; application code should
6
+ use this namespace instead of constructing worker-local paths directly.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ from typing import Any, Dict, Mapping, Optional, TYPE_CHECKING
11
+ from urllib.parse import quote
12
+
13
+ if TYPE_CHECKING:
14
+ from ..start import Dominus
15
+
16
+
17
+ def _compact(payload: Mapping[str, Any]) -> Dict[str, Any]:
18
+ """Drop None and empty-string values."""
19
+ out: Dict[str, Any] = {}
20
+ for key, value in payload.items():
21
+ if value is None:
22
+ continue
23
+ if isinstance(value, str) and value == "":
24
+ continue
25
+ out[key] = value
26
+ return out
27
+
28
+
29
+ class BrowserNamespace:
30
+ """
31
+ Browser automation namespace.
32
+
33
+ Usage::
34
+
35
+ health = await dominus.browser.get_health()
36
+ run = await dominus.browser.ensure_run(
37
+ target={"url": "https://example.com/dashboard"},
38
+ assertions=[{"kind": "status_code", "expected": 200}],
39
+ )
40
+ await dominus.browser.start_run(run["run_id"])
41
+ """
42
+
43
+ def __init__(self, client: "Dominus"):
44
+ self._client = client
45
+
46
+ async def _post(
47
+ self,
48
+ endpoint: str,
49
+ body: Optional[Dict[str, Any]] = None,
50
+ *,
51
+ timeout: float = 30.0,
52
+ ) -> Dict[str, Any]:
53
+ return await self._client._request(
54
+ endpoint=endpoint,
55
+ method="POST",
56
+ body=body or {},
57
+ use_gateway=True,
58
+ timeout=timeout,
59
+ )
60
+
61
+ async def _get(self, endpoint: str, *, timeout: float = 30.0) -> Dict[str, Any]:
62
+ return await self._client._request(
63
+ endpoint=endpoint,
64
+ method="GET",
65
+ use_gateway=True,
66
+ timeout=timeout,
67
+ )
68
+
69
+ async def get_health(self, *, timeout: float = 15.0) -> Dict[str, Any]:
70
+ """Authenticated browser worker health. ``GET /api/browser/health``."""
71
+ return await self._get("/api/browser/health", timeout=timeout)
72
+
73
+ async def health(self, *, timeout: float = 15.0) -> Dict[str, Any]:
74
+ """Alias for :meth:`get_health`."""
75
+ return await self.get_health(timeout=timeout)
76
+
77
+ async def ensure_run(
78
+ self,
79
+ *,
80
+ target: Dict[str, Any],
81
+ idempotency_key: Optional[str] = None,
82
+ run_kind: Optional[str] = None,
83
+ route_label: Optional[str] = None,
84
+ route_policy_ref: Optional[str] = None,
85
+ provider: Optional[str] = None,
86
+ mode: Optional[str] = None,
87
+ auth_ref: Optional[Dict[str, Any]] = None,
88
+ capture_policy: Optional[Dict[str, Any]] = None,
89
+ assertions: Optional[list[Dict[str, Any]]] = None,
90
+ authority: Optional[Dict[str, Any]] = None,
91
+ metadata: Optional[Dict[str, Any]] = None,
92
+ org_id: Optional[str] = None,
93
+ app_slug: Optional[str] = None,
94
+ env: Optional[str] = None,
95
+ timeout: float = 30.0,
96
+ ) -> Dict[str, Any]:
97
+ """
98
+ Ensure a browser run. ``POST /api/browser/runs/ensure``.
99
+
100
+ Browser run metadata remains browser-worker runtime state. Artifact V2
101
+ is only used by the worker for sanitized result/capture payloads.
102
+ """
103
+ if not target:
104
+ raise ValueError("target is required")
105
+ body = _compact({
106
+ "idempotency_key": idempotency_key,
107
+ "run_kind": run_kind,
108
+ "route_label": route_label,
109
+ "route_policy_ref": route_policy_ref,
110
+ "target": target,
111
+ "provider": provider,
112
+ "mode": mode,
113
+ "auth_ref": auth_ref,
114
+ "capture_policy": capture_policy,
115
+ "assertions": assertions,
116
+ "authority": authority,
117
+ "metadata": metadata,
118
+ "org_id": org_id,
119
+ "app_slug": app_slug,
120
+ "env": env,
121
+ })
122
+ return await self._post("/api/browser/runs/ensure", body, timeout=timeout)
123
+
124
+ async def start_run(self, run_id: str, *, timeout: float = 30.0) -> Dict[str, Any]:
125
+ """Start a queued browser run. ``POST /api/browser/runs/{run_id}/start``."""
126
+ if not run_id:
127
+ raise ValueError("run_id is required")
128
+ return await self._post(
129
+ f"/api/browser/runs/{quote(run_id, safe='')}/start",
130
+ {},
131
+ timeout=timeout,
132
+ )
133
+
134
+ async def get_run_status(self, run_id: str, *, timeout: float = 30.0) -> Dict[str, Any]:
135
+ """Read browser run runtime state. ``GET /api/browser/runs/{run_id}/status``."""
136
+ if not run_id:
137
+ raise ValueError("run_id is required")
138
+ return await self._get(
139
+ f"/api/browser/runs/{quote(run_id, safe='')}/status",
140
+ timeout=timeout,
141
+ )
142
+
143
+ async def get_run_result(self, run_id: str, *, timeout: float = 30.0) -> Dict[str, Any]:
144
+ """Read browser run result availability. ``GET /api/browser/runs/{run_id}/result``."""
145
+ if not run_id:
146
+ raise ValueError("run_id is required")
147
+ return await self._get(
148
+ f"/api/browser/runs/{quote(run_id, safe='')}/result",
149
+ timeout=timeout,
150
+ )
151
+
152
+ async def retry_run(self, run_id: str, *, timeout: float = 30.0) -> Dict[str, Any]:
153
+ """Retry a failed or expired browser run. ``POST /api/browser/runs/{run_id}/retry``."""
154
+ if not run_id:
155
+ raise ValueError("run_id is required")
156
+ return await self._post(
157
+ f"/api/browser/runs/{quote(run_id, safe='')}/retry",
158
+ {},
159
+ timeout=timeout,
160
+ )
161
+
162
+ async def nudge_run(self, run_id: str, *, timeout: float = 30.0) -> Dict[str, Any]:
163
+ """Nudge a paused or waiting browser run. ``POST /api/browser/runs/{run_id}/nudge``."""
164
+ if not run_id:
165
+ raise ValueError("run_id is required")
166
+ return await self._post(
167
+ f"/api/browser/runs/{quote(run_id, safe='')}/nudge",
168
+ {},
169
+ timeout=timeout,
170
+ )
171
+
172
+ async def cancel_run(self, run_id: str, *, timeout: float = 30.0) -> Dict[str, Any]:
173
+ """Cancel a non-terminal browser run. ``POST /api/browser/runs/{run_id}/cancel``."""
174
+ if not run_id:
175
+ raise ValueError("run_id is required")
176
+ return await self._post(
177
+ f"/api/browser/runs/{quote(run_id, safe='')}/cancel",
178
+ {},
179
+ timeout=timeout,
180
+ )
181
+
182
+ async def get_run_timeline(self, run_id: str, *, timeout: float = 30.0) -> Dict[str, Any]:
183
+ """Read browser run timeline. ``GET /api/browser/runs/{run_id}/timeline``."""
184
+ if not run_id:
185
+ raise ValueError("run_id is required")
186
+ return await self._get(
187
+ f"/api/browser/runs/{quote(run_id, safe='')}/timeline",
188
+ timeout=timeout,
189
+ )
190
+
191
+ async def get_run_dossier(self, run_id: str, *, timeout: float = 30.0) -> Dict[str, Any]:
192
+ """Read browser run dossier. ``GET /api/browser/runs/{run_id}/dossier``."""
193
+ if not run_id:
194
+ raise ValueError("run_id is required")
195
+ return await self._get(
196
+ f"/api/browser/runs/{quote(run_id, safe='')}/dossier",
197
+ timeout=timeout,
198
+ )
@@ -172,6 +172,10 @@ class Dominus:
172
172
  from .namespaces.authority import AuthorityNamespace
173
173
  self.authority = AuthorityNamespace(self)
174
174
 
175
+ # Browser automation primitive (Cloudflare Browser Run default)
176
+ from .namespaces.browser import BrowserNamespace
177
+ self.browser = BrowserNamespace(self)
178
+
175
179
  # Thin operator control-plane namespaces (Mothership / MCP parity)
176
180
  from .namespaces.deployer import DeployerNamespace
177
181
  from .namespaces.warden import WardenNamespace
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dominus-sdk-python
3
- Version: 4.0.7
3
+ Version: 4.1.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
@@ -42,7 +42,7 @@ Async Python SDK for the Dominus gateway-first service plane.
42
42
  - Gateway-scoped client mode for MCP and other user-JWT sessions
43
43
  - Transport compatibility for wrapped `{success,data}` responses and unwrapped Warden/control-plane success objects
44
44
  - Local helpers for JWT verification, trace propagation, retries, and console capture
45
- - Current package version: `4.0.7`
45
+ - Current package version: `4.0.8`
46
46
 
47
47
  ## Install
48
48
 
@@ -115,6 +115,34 @@ buffer_cleanup = await dominus.logs.run_archive_buffer_maintenance(
115
115
  schedules = await dominus.authority.list_schedules(all_scopes=True)
116
116
  ```
117
117
 
118
+ ## Browser Automation
119
+
120
+ `dominus.browser` exposes the first-class Dominus browser automation primitive through authenticated gateway routes under `/svc/browser/*`. SDK methods use `/api/browser/*` internally with gateway routing enabled; worker-local routes remain `/health` and `/runs/*`.
121
+
122
+ ```python
123
+ health = await dominus.browser.get_health()
124
+ run = await dominus.browser.ensure_run(
125
+ idempotency_key="route-check-1",
126
+ target={"url": "https://example.com/dashboard"},
127
+ provider="auto",
128
+ mode="playwright",
129
+ capture_policy={
130
+ "screenshots": "never",
131
+ "trace": "never",
132
+ "har": "never",
133
+ "video": "never",
134
+ "dom_snapshot": "never",
135
+ "raw_response_bodies": "never",
136
+ "phi_risk": "possible",
137
+ },
138
+ assertions=[{"kind": "status_code", "expected": 200}],
139
+ )
140
+ await dominus.browser.start_run(run["run_id"])
141
+ status = await dominus.browser.get_run_status(run["run_id"])
142
+ ```
143
+
144
+ Cloudflare Browser Run is the default provider. Browserbase is the fallback for future persistent authenticated/HITL work. Browser run metadata is runtime state owned by the browser worker; Artifact V2 is only for sanitized result/capture payloads.
145
+
118
146
  ## Session-Scoped Clients
119
147
 
120
148
  Production MCP and other user-session callers should instantiate `Dominus` with
@@ -169,6 +197,7 @@ JWT and selected scope headers directly through Gateway.
169
197
  | `processor` | Processor | Batch and single-job processing |
170
198
  | `sync` | Sync Worker | KV synchronization |
171
199
  | `authority` | Dominus Authority | Runs, companies, deploys, managed clients, context |
200
+ | `browser` | Browser Worker | Browser run health, ensure/start/status/result/retry/nudge/cancel/timeline/dossier |
172
201
  | `deployer` | Deployer | Thin operator control-plane request surface |
173
202
  | `warden` | Warden | Thin operator control-plane request surface |
174
203
  | `fastapi` | Local decorators | `@jwt`, `@psk`, `@scopes(...)` |
@@ -19,6 +19,7 @@ dominus/namespaces/ai.py
19
19
  dominus/namespaces/artifacts.py
20
20
  dominus/namespaces/auth.py
21
21
  dominus/namespaces/authority.py
22
+ dominus/namespaces/browser.py
22
23
  dominus/namespaces/courier.py
23
24
  dominus/namespaces/db.py
24
25
  dominus/namespaces/ddl.py
@@ -44,6 +45,7 @@ dominus_sdk_python.egg-info/requires.txt
44
45
  dominus_sdk_python.egg-info/top_level.txt
45
46
  tests/test_auth.py
46
47
  tests/test_authority_public_vocabulary.py
48
+ tests/test_browser_namespace.py
47
49
  tests/test_control_plane_namespaces.py
48
50
  tests/test_errors.py
49
51
  tests/test_flat_commands.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "dominus-sdk-python"
7
- version = "4.0.7"
7
+ version = "4.1.0"
8
8
  description = "Python SDK for the Dominus gateway-first platform"
9
9
  readme = "README.md"
10
10
  license = {text = "Proprietary"}
@@ -0,0 +1,107 @@
1
+ import pytest
2
+
3
+ import dominus.start as start_module
4
+
5
+
6
+ @pytest.fixture()
7
+ def sdk(monkeypatch):
8
+ monkeypatch.setattr(start_module, "_VALIDATION_ERROR", None)
9
+ monkeypatch.setattr(start_module, "_TOKEN", "a" * 64)
10
+ monkeypatch.setattr(start_module, "_VALIDATED", False)
11
+ monkeypatch.setattr(start_module, "_BASE_URL", "https://gateway.example")
12
+ monkeypatch.setattr(start_module, "_GATEWAY_URL", "https://gateway.example")
13
+ return start_module.Dominus()
14
+
15
+
16
+ @pytest.mark.asyncio
17
+ async def test_browser_namespace_uses_gateway_routed_api_browser_paths(monkeypatch, sdk):
18
+ calls = []
19
+
20
+ async def fake_request(**kwargs):
21
+ calls.append(kwargs)
22
+ return {
23
+ "ok": True,
24
+ "run_id": "br_123",
25
+ "status": "queued",
26
+ "provider": "cloudflare_browser_run",
27
+ "mode": "playwright",
28
+ }
29
+
30
+ monkeypatch.setattr(sdk, "_request", fake_request)
31
+
32
+ await sdk.browser.get_health()
33
+ await sdk.browser.ensure_run(
34
+ idempotency_key="idem-1",
35
+ run_kind="browser.route_check",
36
+ route_label="summit-dashboard",
37
+ route_policy_ref="rrc.v1:project",
38
+ target={"url": "https://summit.example/dashboard?token=redacted"},
39
+ provider="auto",
40
+ mode="playwright",
41
+ auth_ref={
42
+ "kind": "dominus_secret",
43
+ "secret_ref": "warden://browser/summit/session",
44
+ "expected_project_id": "project-123",
45
+ "expected_scopes": ["browser.run"],
46
+ "expected_view": "dashboard",
47
+ "session_probe": "#app",
48
+ },
49
+ capture_policy={
50
+ "screenshots": "on_failure",
51
+ "dom_snapshot": "always",
52
+ "raw_response_bodies": "always",
53
+ "phi_risk": "likely",
54
+ "retention_seconds": 900,
55
+ },
56
+ assertions=[
57
+ {"kind": "status_code", "expected": 200},
58
+ {"kind": "selector_visible", "selector": "#app", "timeout_ms": 5000},
59
+ ],
60
+ authority={"run_id": "auth-run-1", "artifact_observations": True},
61
+ metadata={"route": "dashboard"},
62
+ )
63
+ await sdk.browser.start_run("br_123")
64
+ await sdk.browser.get_run_status("br_123")
65
+ await sdk.browser.get_run_result("br_123")
66
+ await sdk.browser.retry_run("br_123")
67
+ await sdk.browser.nudge_run("br_123")
68
+ await sdk.browser.cancel_run("br_123")
69
+ await sdk.browser.get_run_timeline("br_123")
70
+ await sdk.browser.get_run_dossier("br_123")
71
+
72
+ assert [(call["method"], call["endpoint"], call["use_gateway"]) for call in calls] == [
73
+ ("GET", "/api/browser/health", True),
74
+ ("POST", "/api/browser/runs/ensure", True),
75
+ ("POST", "/api/browser/runs/br_123/start", True),
76
+ ("GET", "/api/browser/runs/br_123/status", True),
77
+ ("GET", "/api/browser/runs/br_123/result", True),
78
+ ("POST", "/api/browser/runs/br_123/retry", True),
79
+ ("POST", "/api/browser/runs/br_123/nudge", True),
80
+ ("POST", "/api/browser/runs/br_123/cancel", True),
81
+ ("GET", "/api/browser/runs/br_123/timeline", True),
82
+ ("GET", "/api/browser/runs/br_123/dossier", True),
83
+ ]
84
+
85
+ ensure_body = calls[1]["body"]
86
+ assert ensure_body["idempotency_key"] == "idem-1"
87
+ assert ensure_body["run_kind"] == "browser.route_check"
88
+ assert ensure_body["route_label"] == "summit-dashboard"
89
+ assert ensure_body["route_policy_ref"] == "rrc.v1:project"
90
+ assert ensure_body["auth_ref"]["secret_ref"] == "warden://browser/summit/session"
91
+ assert ensure_body["capture_policy"]["dom_snapshot"] == "always"
92
+ assert ensure_body["capture_policy"]["raw_response_bodies"] == "always"
93
+ assert ensure_body["assertions"][1] == {
94
+ "kind": "selector_visible",
95
+ "selector": "#app",
96
+ "timeout_ms": 5000,
97
+ }
98
+ assert ensure_body["authority"] == {
99
+ "run_id": "auth-run-1",
100
+ "artifact_observations": True,
101
+ }
102
+
103
+
104
+ @pytest.mark.asyncio
105
+ async def test_browser_ensure_run_requires_target(sdk):
106
+ with pytest.raises(ValueError, match="target is required"):
107
+ await sdk.browser.ensure_run(target={})
@@ -1,4 +1,5 @@
1
1
  from dominus import (
2
+ BrowserNamespace,
2
3
  DeployerNamespace,
3
4
  WardenNamespace,
4
5
  gateway_circuit_breaker,
@@ -29,3 +30,10 @@ def test_top_level_exports_drop_delegate_alias():
29
30
  assert OpenNamespace is None
30
31
  assert DeployerNamespace is not None
31
32
  assert WardenNamespace is not None
33
+ assert BrowserNamespace is not None
34
+
35
+
36
+ def test_singleton_exposes_browser_namespace():
37
+ from dominus import dominus
38
+
39
+ assert callable(dominus.browser.get_health)