dominus-sdk-python 7.0.0__tar.gz → 7.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 (73) hide show
  1. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/PKG-INFO +51 -3
  2. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/README.md +50 -2
  3. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/dominus/__init__.py +3 -2
  4. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/dominus/namespaces/__init__.py +2 -1
  5. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/dominus/namespaces/stash.py +325 -1
  6. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/dominus_sdk_python.egg-info/PKG-INFO +51 -3
  7. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/dominus_sdk_python.egg-info/SOURCES.txt +2 -0
  8. dominus_sdk_python-7.1.0/dominus_sdk_python.egg-info/dependency_links.txt +1 -0
  9. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/pyproject.toml +1 -1
  10. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/tests/test_namespace_barrel_exports.py +15 -0
  11. dominus_sdk_python-7.1.0/tests/test_stash_tables_namespace.py +294 -0
  12. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/dominus/config/__init__.py +0 -0
  13. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/dominus/config/endpoints.py +0 -0
  14. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/dominus/errors.py +0 -0
  15. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/dominus/helpers/__init__.py +0 -0
  16. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/dominus/helpers/auth.py +0 -0
  17. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/dominus/helpers/cache.py +0 -0
  18. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/dominus/helpers/console_capture.py +0 -0
  19. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/dominus/helpers/core.py +0 -0
  20. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/dominus/helpers/crypto.py +0 -0
  21. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/dominus/helpers/sse.py +0 -0
  22. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/dominus/helpers/trace.py +0 -0
  23. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/dominus/namespaces/admin.py +0 -0
  24. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/dominus/namespaces/ai.py +0 -0
  25. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/dominus/namespaces/artifacts.py +0 -0
  26. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/dominus/namespaces/auth.py +0 -0
  27. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/dominus/namespaces/authority.py +0 -0
  28. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/dominus/namespaces/browser.py +0 -0
  29. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/dominus/namespaces/coder.py +0 -0
  30. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/dominus/namespaces/courier.py +0 -0
  31. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/dominus/namespaces/db.py +0 -0
  32. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/dominus/namespaces/ddl.py +0 -0
  33. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/dominus/namespaces/deployer.py +0 -0
  34. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/dominus/namespaces/fastapi.py +0 -0
  35. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/dominus/namespaces/files.py +0 -0
  36. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/dominus/namespaces/health.py +0 -0
  37. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/dominus/namespaces/jobs.py +0 -0
  38. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/dominus/namespaces/logs.py +0 -0
  39. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/dominus/namespaces/platform.py +0 -0
  40. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/dominus/namespaces/portal.py +0 -0
  41. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/dominus/namespaces/processor.py +0 -0
  42. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/dominus/namespaces/publisher.py +0 -0
  43. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/dominus/namespaces/recipes.py +0 -0
  44. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/dominus/namespaces/redis.py +0 -0
  45. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/dominus/namespaces/secrets.py +0 -0
  46. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/dominus/namespaces/secure.py +0 -0
  47. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/dominus/namespaces/sync.py +0 -0
  48. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/dominus/namespaces/warden.py +0 -0
  49. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/dominus/namespaces/workflow.py +0 -0
  50. /dominus_sdk_python-7.0.0/dominus_sdk_python.egg-info/dependency_links.txt → /dominus_sdk_python-7.1.0/dominus/py.typed +0 -0
  51. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/dominus/services/__init__.py +0 -0
  52. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/dominus/start.py +0 -0
  53. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/dominus_sdk_python.egg-info/requires.txt +0 -0
  54. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/dominus_sdk_python.egg-info/top_level.txt +0 -0
  55. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/setup.cfg +0 -0
  56. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/tests/test_auth.py +0 -0
  57. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/tests/test_authority_public_vocabulary.py +0 -0
  58. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/tests/test_browser_namespace.py +0 -0
  59. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/tests/test_control_plane_namespaces.py +0 -0
  60. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/tests/test_errors.py +0 -0
  61. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/tests/test_flat_commands.py +0 -0
  62. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/tests/test_health.py +0 -0
  63. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/tests/test_logs.py +0 -0
  64. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/tests/test_platform_coder_namespaces.py +0 -0
  65. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/tests/test_provisioning_parity.py +0 -0
  66. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/tests/test_public_exports.py +0 -0
  67. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/tests/test_publisher_namespace.py +0 -0
  68. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/tests/test_recipes_namespace.py +0 -0
  69. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/tests/test_recipes_stash_routing.py +0 -0
  70. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/tests/test_stash_artifact_facade.py +0 -0
  71. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/tests/test_transport_compat.py +0 -0
  72. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.1.0}/tests/test_workflow_lifecycle.py +0 -0
  73. {dominus_sdk_python-7.0.0 → dominus_sdk_python-7.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: 7.0.0
3
+ Version: 7.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-Expression: LicenseRef-Proprietary
@@ -48,7 +48,7 @@ cleanup audit is `docs/janitor/2026-06-27-0849-agent-guide-cleanup-audit.md`.
48
48
  - Gateway-scoped client mode for MCP and other user-JWT sessions
49
49
  - Transport compatibility for wrapped `{success,data}` responses and unwrapped Warden/control-plane success objects
50
50
  - Local helpers for JWT verification, trace propagation, retries, and console capture
51
- - Current package version: `7.0.0`
51
+ - Current package version: `7.1.0`
52
52
 
53
53
  ## Install
54
54
 
@@ -141,6 +141,54 @@ buffer_cleanup = await dominus.logs.run_archive_buffer_maintenance(
141
141
  schedules = await dominus.authority.list_schedules(all_scopes=True)
142
142
  ```
143
143
 
144
+ ## Stash Managed Tables
145
+
146
+ `dominus.stash.tables` provides a typed client for small, scoped JSONB tables
147
+ without exposing SQL. Every call requires `env`, `scope` (`self` or `group`),
148
+ and `table`; self and group tables resolve to independent project and shared
149
+ databases.
150
+
151
+ ```python
152
+ spec = await dominus.stash.tables.define(
153
+ env="production",
154
+ scope="self",
155
+ table="jobs",
156
+ if_revision=0,
157
+ primary_key={"field": "id", "generate": "uuid"},
158
+ fields={
159
+ "id": {"type": "uuid", "required": True},
160
+ "status": {"type": "string", "required": True},
161
+ "priority": {"type": "integer"},
162
+ },
163
+ )
164
+
165
+ row = await dominus.stash.tables.put_row(
166
+ env="production",
167
+ scope="self",
168
+ table="jobs",
169
+ if_revision=0,
170
+ row={"status": "queued", "priority": 5},
171
+ )
172
+
173
+ page = await dominus.stash.tables.select(
174
+ env="production",
175
+ scope="self",
176
+ table="jobs",
177
+ where={
178
+ "or": [
179
+ {"field": "status", "op": "eq", "value": "queued"},
180
+ {"field": "priority", "op": "gte", "value": 5},
181
+ ]
182
+ },
183
+ page_size=50,
184
+ )
185
+ ```
186
+
187
+ Use `if_revision` for table definitions and single-row put/delete compare-and-
188
+ swap. `update_where` instead requires `if_spec_revision` and `max_affected`.
189
+ Cursors are opaque keyset tokens. `delete_row` is a hard delete and returns a
190
+ durable mutation receipt; there is no soft-delete or restore lifecycle.
191
+
144
192
  ## Browser Automation
145
193
 
146
194
  `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/*`.
@@ -230,7 +278,7 @@ JWT and selected scope headers directly through Gateway.
230
278
  | `platform` | Platform Worker | Group/repository policy decisions with actor attribution |
231
279
  | `coder` | Coder Runtime | Policy-bound Coder run lifecycle with workflow/pipeline recipe launch sources |
232
280
  | `publisher` | Publisher | Release/channel/signing build and artifact operations |
233
- | `stash` | Stash | Stash items, kinds, bookmarks, watches, artifact facade |
281
+ | `stash` | Stash | Items, managed tables, kinds, bookmarks, watches, artifact facade |
234
282
  | `recipes` | Recipe Worker | Recipe type registry, publish, validate, get/list |
235
283
  | `fastapi` | Local decorators | `@jwt`, `@psk`, `@scopes(...)` |
236
284
 
@@ -15,7 +15,7 @@ cleanup audit is `docs/janitor/2026-06-27-0849-agent-guide-cleanup-audit.md`.
15
15
  - Gateway-scoped client mode for MCP and other user-JWT sessions
16
16
  - Transport compatibility for wrapped `{success,data}` responses and unwrapped Warden/control-plane success objects
17
17
  - Local helpers for JWT verification, trace propagation, retries, and console capture
18
- - Current package version: `7.0.0`
18
+ - Current package version: `7.1.0`
19
19
 
20
20
  ## Install
21
21
 
@@ -108,6 +108,54 @@ buffer_cleanup = await dominus.logs.run_archive_buffer_maintenance(
108
108
  schedules = await dominus.authority.list_schedules(all_scopes=True)
109
109
  ```
110
110
 
111
+ ## Stash Managed Tables
112
+
113
+ `dominus.stash.tables` provides a typed client for small, scoped JSONB tables
114
+ without exposing SQL. Every call requires `env`, `scope` (`self` or `group`),
115
+ and `table`; self and group tables resolve to independent project and shared
116
+ databases.
117
+
118
+ ```python
119
+ spec = await dominus.stash.tables.define(
120
+ env="production",
121
+ scope="self",
122
+ table="jobs",
123
+ if_revision=0,
124
+ primary_key={"field": "id", "generate": "uuid"},
125
+ fields={
126
+ "id": {"type": "uuid", "required": True},
127
+ "status": {"type": "string", "required": True},
128
+ "priority": {"type": "integer"},
129
+ },
130
+ )
131
+
132
+ row = await dominus.stash.tables.put_row(
133
+ env="production",
134
+ scope="self",
135
+ table="jobs",
136
+ if_revision=0,
137
+ row={"status": "queued", "priority": 5},
138
+ )
139
+
140
+ page = await dominus.stash.tables.select(
141
+ env="production",
142
+ scope="self",
143
+ table="jobs",
144
+ where={
145
+ "or": [
146
+ {"field": "status", "op": "eq", "value": "queued"},
147
+ {"field": "priority", "op": "gte", "value": 5},
148
+ ]
149
+ },
150
+ page_size=50,
151
+ )
152
+ ```
153
+
154
+ Use `if_revision` for table definitions and single-row put/delete compare-and-
155
+ swap. `update_where` instead requires `if_spec_revision` and `max_affected`.
156
+ Cursors are opaque keyset tokens. `delete_row` is a hard delete and returns a
157
+ durable mutation receipt; there is no soft-delete or restore lifecycle.
158
+
111
159
  ## Browser Automation
112
160
 
113
161
  `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/*`.
@@ -197,7 +245,7 @@ JWT and selected scope headers directly through Gateway.
197
245
  | `platform` | Platform Worker | Group/repository policy decisions with actor attribution |
198
246
  | `coder` | Coder Runtime | Policy-bound Coder run lifecycle with workflow/pipeline recipe launch sources |
199
247
  | `publisher` | Publisher | Release/channel/signing build and artifact operations |
200
- | `stash` | Stash | Stash items, kinds, bookmarks, watches, artifact facade |
248
+ | `stash` | Stash | Items, managed tables, kinds, bookmarks, watches, artifact facade |
201
249
  | `recipes` | Recipe Worker | Recipe type registry, publish, validate, get/list |
202
250
  | `fastapi` | Local decorators | `@jwt`, `@psk`, `@scopes(...)` |
203
251
 
@@ -115,7 +115,7 @@ from .namespaces.warden import WardenNamespace
115
115
  from .namespaces.platform import PlatformNamespace
116
116
  from .namespaces.coder import CoderNamespace
117
117
  from .namespaces.publisher import PublisherNamespace
118
- from .namespaces.stash import StashNamespace
118
+ from .namespaces.stash import StashNamespace, StashTablesNamespace
119
119
 
120
120
  # Export AI namespace for agent-runtime operations
121
121
  from .namespaces.ai import (
@@ -173,7 +173,7 @@ from .errors import (
173
173
  TimeoutError as DominusTimeoutError,
174
174
  )
175
175
 
176
- __version__ = "7.0.0"
176
+ __version__ = "7.1.0"
177
177
  __all__ = [
178
178
  # Main SDK instance
179
179
  "dominus",
@@ -228,6 +228,7 @@ __all__ = [
228
228
  "CoderNamespace",
229
229
  "PublisherNamespace",
230
230
  "StashNamespace",
231
+ "StashTablesNamespace",
231
232
  # AI namespace for agent-runtime operations
232
233
  "AiNamespace",
233
234
  "RagSubNamespace",
@@ -22,7 +22,7 @@ from .warden import WardenNamespace
22
22
  from .platform import PlatformNamespace
23
23
  from .coder import CoderNamespace
24
24
  from .publisher import PublisherNamespace
25
- from .stash import StashNamespace
25
+ from .stash import StashNamespace, StashTablesNamespace
26
26
  from .ai import (
27
27
  AiNamespace,
28
28
  RagSubNamespace,
@@ -56,6 +56,7 @@ __all__ = [
56
56
  "CoderNamespace",
57
57
  "PublisherNamespace",
58
58
  "StashNamespace",
59
+ "StashTablesNamespace",
59
60
  "AiNamespace",
60
61
  "RagSubNamespace",
61
62
  "ArtifactsSubNamespace",
@@ -5,7 +5,17 @@ Routes through ``/svc/stash/*`` on the Dominus gateway. The stash worker
5
5
  stores items in each project's ``stash.*`` schema and transparently falls
6
6
  back to a designated shared project on read.
7
7
  """
8
- from typing import Any, Dict, List, Optional, TYPE_CHECKING
8
+ from typing import (
9
+ Any,
10
+ Dict,
11
+ List,
12
+ Literal,
13
+ Mapping,
14
+ Optional,
15
+ TYPE_CHECKING,
16
+ TypedDict,
17
+ Union,
18
+ )
9
19
  from urllib.parse import urlencode
10
20
 
11
21
  if TYPE_CHECKING:
@@ -14,11 +24,324 @@ if TYPE_CHECKING:
14
24
  BookmarkRef = Dict[str, Any]
15
25
  WatcherRef = Dict[str, Any]
16
26
 
27
+ ManagedTableEnv = Literal["development", "staging", "production"]
28
+ ManagedTableScope = Literal["self", "group"]
29
+ ManagedTableFieldType = Literal[
30
+ "string",
31
+ "uuid",
32
+ "number",
33
+ "integer",
34
+ "boolean",
35
+ "timestamp",
36
+ "json",
37
+ ]
38
+ ManagedTablePrimaryKeyGeneration = Literal["none", "uuid"]
39
+ ManagedTableEnumValue = Union[str, int, float, bool]
40
+ ManagedTableOperator = Literal[
41
+ "eq",
42
+ "ne",
43
+ "in",
44
+ "prefix",
45
+ "contains",
46
+ "is_null",
47
+ "gt",
48
+ "gte",
49
+ "lt",
50
+ "lte",
51
+ "between",
52
+ "before",
53
+ "after",
54
+ ]
55
+
56
+
57
+ class _ManagedTableFieldOptional(TypedDict, total=False):
58
+ required: bool
59
+ nullable: bool
60
+ enum: List[ManagedTableEnumValue]
61
+ min_length: int
62
+ max_length: int
63
+ min: Union[int, float]
64
+ max: Union[int, float]
65
+
66
+
67
+ class ManagedTableField(_ManagedTableFieldOptional):
68
+ type: ManagedTableFieldType
69
+
70
+
71
+ class ManagedTablePrimaryKey(TypedDict):
72
+ field: str
73
+ generate: ManagedTablePrimaryKeyGeneration
74
+
75
+
76
+ class ManagedTableSpec(TypedDict):
77
+ table: str
78
+ revision: int
79
+ primary_key: ManagedTablePrimaryKey
80
+ fields: Dict[str, ManagedTableField]
81
+ created_at: str
82
+ updated_at: str
83
+ scope: ManagedTableScope
84
+
85
+
86
+ class ManagedTableRowView(TypedDict):
87
+ row_key: str
88
+ data: Dict[str, Any]
89
+ revision: int
90
+ schema_revision: int
91
+ stale_schema: bool
92
+ created_at: str
93
+ updated_at: str
94
+
95
+
96
+ class ManagedTableRow(ManagedTableRowView):
97
+ table: str
98
+ scope: ManagedTableScope
99
+
100
+
101
+ class _ManagedTablePredicateOptional(TypedDict, total=False):
102
+ value: Any
103
+
104
+
105
+ class ManagedTablePredicate(_ManagedTablePredicateOptional):
106
+ field: str
107
+ op: ManagedTableOperator
108
+
109
+
110
+ ManagedTableLeafAndGroup = TypedDict(
111
+ "ManagedTableLeafAndGroup",
112
+ {"and": List[ManagedTablePredicate]},
113
+ )
114
+ ManagedTableLeafOrGroup = TypedDict(
115
+ "ManagedTableLeafOrGroup",
116
+ {"or": List[ManagedTablePredicate]},
117
+ )
118
+ ManagedTableLeafGroup = Union[ManagedTableLeafAndGroup, ManagedTableLeafOrGroup]
119
+ ManagedTableWhereClause = Union[ManagedTablePredicate, ManagedTableLeafGroup]
120
+ ManagedTableAndGroup = TypedDict(
121
+ "ManagedTableAndGroup",
122
+ {"and": List[ManagedTableWhereClause]},
123
+ )
124
+ ManagedTableOrGroup = TypedDict(
125
+ "ManagedTableOrGroup",
126
+ {"or": List[ManagedTableWhereClause]},
127
+ )
128
+ ManagedTableWhere = Union[
129
+ List[ManagedTableWhereClause],
130
+ ManagedTableAndGroup,
131
+ ManagedTableOrGroup,
132
+ ]
133
+
134
+
135
+ class ManagedTableOrderBy(TypedDict):
136
+ field: str
137
+ direction: Literal["asc", "desc"]
138
+
139
+
140
+ class ManagedTableSelectResult(TypedDict):
141
+ rows: List[ManagedTableRowView]
142
+ next_cursor: Optional[str]
143
+ spec_revision: int
144
+ table: str
145
+ scope: ManagedTableScope
146
+
147
+
148
+ class ManagedTableMutationReceipt(TypedDict):
149
+ mutation_id: str
150
+ created_at: str
151
+
152
+
153
+ class _ManagedTableMutationRowOptional(TypedDict, total=False):
154
+ updated_at: str
155
+
156
+
157
+ class ManagedTableMutationRow(_ManagedTableMutationRowOptional):
158
+ row_key: str
159
+ revision: int
160
+ schema_revision: int
161
+
162
+
163
+ class ManagedTableUpdateResult(TypedDict):
164
+ matched_count: int
165
+ affected_count: int
166
+ rows: List[ManagedTableMutationRow]
167
+ receipt: ManagedTableMutationReceipt
168
+ table: str
169
+ scope: ManagedTableScope
170
+
171
+
172
+ class ManagedTableDeleteResult(TypedDict):
173
+ deleted: Literal[True]
174
+ row_key: str
175
+ revision: int
176
+ schema_revision: int
177
+ receipt: ManagedTableMutationReceipt
178
+ table: str
179
+ scope: ManagedTableScope
180
+
17
181
 
18
182
  def _facade_query(*, env: str, kind: str, scope: str, key: str) -> str:
19
183
  return urlencode({"env": env, "kind": kind, "scope": scope, "key": key})
20
184
 
21
185
 
186
+ class StashTablesNamespace:
187
+ """Typed client for Stash-managed JSONB tables."""
188
+
189
+ def __init__(self, client: "Dominus"):
190
+ self._client = client
191
+
192
+ async def define(
193
+ self,
194
+ *,
195
+ env: ManagedTableEnv,
196
+ scope: ManagedTableScope,
197
+ table: str,
198
+ if_revision: int,
199
+ primary_key: ManagedTablePrimaryKey,
200
+ fields: Mapping[str, ManagedTableField],
201
+ ) -> ManagedTableSpec:
202
+ """Create or revision-guard a managed table definition."""
203
+ return await self._client._request(
204
+ endpoint="/svc/stash/tables/define",
205
+ method="POST",
206
+ body={
207
+ "env": env,
208
+ "scope": scope,
209
+ "table": table,
210
+ "if_revision": if_revision,
211
+ "primary_key": dict(primary_key),
212
+ "fields": dict(fields),
213
+ },
214
+ use_gateway=True,
215
+ )
216
+
217
+ async def put_row(
218
+ self,
219
+ *,
220
+ env: ManagedTableEnv,
221
+ scope: ManagedTableScope,
222
+ table: str,
223
+ if_revision: int,
224
+ row: Mapping[str, Any],
225
+ ) -> ManagedTableRow:
226
+ """Create or fully replace one row under exact row revision CAS."""
227
+ return await self._client._request(
228
+ endpoint="/svc/stash/tables/put-row",
229
+ method="POST",
230
+ body={
231
+ "env": env,
232
+ "scope": scope,
233
+ "table": table,
234
+ "if_revision": if_revision,
235
+ "row": dict(row),
236
+ },
237
+ use_gateway=True,
238
+ )
239
+
240
+ async def get_row(
241
+ self,
242
+ *,
243
+ env: ManagedTableEnv,
244
+ scope: ManagedTableScope,
245
+ table: str,
246
+ key: Union[str, int],
247
+ ) -> ManagedTableRow:
248
+ """Read one managed-table row by its declared primary key."""
249
+ return await self._client._request(
250
+ endpoint="/svc/stash/tables/get-row",
251
+ method="POST",
252
+ body={
253
+ "env": env,
254
+ "scope": scope,
255
+ "table": table,
256
+ "key": key,
257
+ },
258
+ use_gateway=True,
259
+ )
260
+
261
+ async def select(
262
+ self,
263
+ *,
264
+ env: ManagedTableEnv,
265
+ scope: ManagedTableScope,
266
+ table: str,
267
+ where: Optional[ManagedTableWhere] = None,
268
+ order_by: Optional[ManagedTableOrderBy] = None,
269
+ page_size: Optional[int] = None,
270
+ cursor: Optional[str] = None,
271
+ ) -> ManagedTableSelectResult:
272
+ """Select one keyset page using the bounded managed-table grammar."""
273
+ body: Dict[str, Any] = {"env": env, "scope": scope, "table": table}
274
+ if where is not None:
275
+ body["where"] = where
276
+ if order_by is not None:
277
+ body["order_by"] = dict(order_by)
278
+ if page_size is not None:
279
+ body["page_size"] = page_size
280
+ if cursor is not None:
281
+ body["cursor"] = cursor
282
+ return await self._client._request(
283
+ endpoint="/svc/stash/tables/select",
284
+ method="POST",
285
+ body=body,
286
+ use_gateway=True,
287
+ )
288
+
289
+ async def update_where(
290
+ self,
291
+ *,
292
+ env: ManagedTableEnv,
293
+ scope: ManagedTableScope,
294
+ table: str,
295
+ where: ManagedTableWhere,
296
+ if_spec_revision: int,
297
+ max_affected: int,
298
+ set: Optional[Mapping[str, Any]] = None,
299
+ remove: Optional[List[str]] = None,
300
+ ) -> ManagedTableUpdateResult:
301
+ """Patch a bounded match set under exact active-spec revision CAS."""
302
+ body: Dict[str, Any] = {
303
+ "env": env,
304
+ "scope": scope,
305
+ "table": table,
306
+ "where": where,
307
+ "if_spec_revision": if_spec_revision,
308
+ "max_affected": max_affected,
309
+ }
310
+ if set is not None:
311
+ body["set"] = dict(set)
312
+ if remove is not None:
313
+ body["remove"] = list(remove)
314
+ return await self._client._request(
315
+ endpoint="/svc/stash/tables/update-where",
316
+ method="POST",
317
+ body=body,
318
+ use_gateway=True,
319
+ )
320
+
321
+ async def delete_row(
322
+ self,
323
+ *,
324
+ env: ManagedTableEnv,
325
+ scope: ManagedTableScope,
326
+ table: str,
327
+ key: Union[str, int],
328
+ if_revision: int,
329
+ ) -> ManagedTableDeleteResult:
330
+ """Hard-delete one row under exact row revision CAS."""
331
+ return await self._client._request(
332
+ endpoint="/svc/stash/tables/delete-row",
333
+ method="POST",
334
+ body={
335
+ "env": env,
336
+ "scope": scope,
337
+ "table": table,
338
+ "key": key,
339
+ "if_revision": if_revision,
340
+ },
341
+ use_gateway=True,
342
+ )
343
+
344
+
22
345
  class StashNamespace:
23
346
  """
24
347
  Stash worker namespace.
@@ -34,6 +357,7 @@ class StashNamespace:
34
357
 
35
358
  def __init__(self, client: "Dominus"):
36
359
  self._client = client
360
+ self.tables = StashTablesNamespace(client)
37
361
 
38
362
  async def upsert(
39
363
  self,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dominus-sdk-python
3
- Version: 7.0.0
3
+ Version: 7.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-Expression: LicenseRef-Proprietary
@@ -48,7 +48,7 @@ cleanup audit is `docs/janitor/2026-06-27-0849-agent-guide-cleanup-audit.md`.
48
48
  - Gateway-scoped client mode for MCP and other user-JWT sessions
49
49
  - Transport compatibility for wrapped `{success,data}` responses and unwrapped Warden/control-plane success objects
50
50
  - Local helpers for JWT verification, trace propagation, retries, and console capture
51
- - Current package version: `7.0.0`
51
+ - Current package version: `7.1.0`
52
52
 
53
53
  ## Install
54
54
 
@@ -141,6 +141,54 @@ buffer_cleanup = await dominus.logs.run_archive_buffer_maintenance(
141
141
  schedules = await dominus.authority.list_schedules(all_scopes=True)
142
142
  ```
143
143
 
144
+ ## Stash Managed Tables
145
+
146
+ `dominus.stash.tables` provides a typed client for small, scoped JSONB tables
147
+ without exposing SQL. Every call requires `env`, `scope` (`self` or `group`),
148
+ and `table`; self and group tables resolve to independent project and shared
149
+ databases.
150
+
151
+ ```python
152
+ spec = await dominus.stash.tables.define(
153
+ env="production",
154
+ scope="self",
155
+ table="jobs",
156
+ if_revision=0,
157
+ primary_key={"field": "id", "generate": "uuid"},
158
+ fields={
159
+ "id": {"type": "uuid", "required": True},
160
+ "status": {"type": "string", "required": True},
161
+ "priority": {"type": "integer"},
162
+ },
163
+ )
164
+
165
+ row = await dominus.stash.tables.put_row(
166
+ env="production",
167
+ scope="self",
168
+ table="jobs",
169
+ if_revision=0,
170
+ row={"status": "queued", "priority": 5},
171
+ )
172
+
173
+ page = await dominus.stash.tables.select(
174
+ env="production",
175
+ scope="self",
176
+ table="jobs",
177
+ where={
178
+ "or": [
179
+ {"field": "status", "op": "eq", "value": "queued"},
180
+ {"field": "priority", "op": "gte", "value": 5},
181
+ ]
182
+ },
183
+ page_size=50,
184
+ )
185
+ ```
186
+
187
+ Use `if_revision` for table definitions and single-row put/delete compare-and-
188
+ swap. `update_where` instead requires `if_spec_revision` and `max_affected`.
189
+ Cursors are opaque keyset tokens. `delete_row` is a hard delete and returns a
190
+ durable mutation receipt; there is no soft-delete or restore lifecycle.
191
+
144
192
  ## Browser Automation
145
193
 
146
194
  `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/*`.
@@ -230,7 +278,7 @@ JWT and selected scope headers directly through Gateway.
230
278
  | `platform` | Platform Worker | Group/repository policy decisions with actor attribution |
231
279
  | `coder` | Coder Runtime | Policy-bound Coder run lifecycle with workflow/pipeline recipe launch sources |
232
280
  | `publisher` | Publisher | Release/channel/signing build and artifact operations |
233
- | `stash` | Stash | Stash items, kinds, bookmarks, watches, artifact facade |
281
+ | `stash` | Stash | Items, managed tables, kinds, bookmarks, watches, artifact facade |
234
282
  | `recipes` | Recipe Worker | Recipe type registry, publish, validate, get/list |
235
283
  | `fastapi` | Local decorators | `@jwt`, `@psk`, `@scopes(...)` |
236
284
 
@@ -2,6 +2,7 @@ README.md
2
2
  pyproject.toml
3
3
  dominus/__init__.py
4
4
  dominus/errors.py
5
+ dominus/py.typed
5
6
  dominus/start.py
6
7
  dominus/config/__init__.py
7
8
  dominus/config/endpoints.py
@@ -64,6 +65,7 @@ tests/test_publisher_namespace.py
64
65
  tests/test_recipes_namespace.py
65
66
  tests/test_recipes_stash_routing.py
66
67
  tests/test_stash_artifact_facade.py
68
+ tests/test_stash_tables_namespace.py
67
69
  tests/test_transport_compat.py
68
70
  tests/test_workflow_lifecycle.py
69
71
  tests/test_workflow_refs.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "dominus-sdk-python"
7
- version = "7.0.0"
7
+ version = "7.1.0"
8
8
  description = "Python SDK for the Dominus gateway-first platform"
9
9
  readme = "README.md"
10
10
  license = "LicenseRef-Proprietary"
@@ -1,3 +1,5 @@
1
+ from pathlib import Path
2
+
1
3
  import dominus
2
4
  import dominus.namespaces
3
5
 
@@ -24,3 +26,16 @@ def test_top_level_all_lists_stash_and_publisher():
24
26
  def test_namespaces_all_lists_stash_and_publisher():
25
27
  assert "StashNamespace" in dominus.namespaces.__all__
26
28
  assert "PublisherNamespace" in dominus.namespaces.__all__
29
+
30
+
31
+ def test_stash_tables_namespace_is_exported_from_both_barrels():
32
+ from dominus import StashTablesNamespace as TopLevelStashTablesNamespace
33
+ from dominus.namespaces import StashTablesNamespace as NamespaceStashTablesNamespace
34
+
35
+ assert TopLevelStashTablesNamespace is NamespaceStashTablesNamespace
36
+ assert "StashTablesNamespace" in dominus.__all__
37
+ assert "StashTablesNamespace" in dominus.namespaces.__all__
38
+
39
+
40
+ def test_package_includes_declared_pep561_marker():
41
+ assert Path(dominus.__file__).with_name("py.typed").is_file()
@@ -0,0 +1,294 @@
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_define_managed_table_posts_exact_worker_body(monkeypatch, sdk):
18
+ calls = []
19
+
20
+ async def fake_request(**kwargs):
21
+ calls.append(kwargs)
22
+ return {"table": "jobs", "revision": 1, "scope": "self"}
23
+
24
+ monkeypatch.setattr(sdk, "_request", fake_request)
25
+
26
+ result = await sdk.stash.tables.define(
27
+ env="production",
28
+ scope="self",
29
+ table="jobs",
30
+ if_revision=0,
31
+ primary_key={"field": "id", "generate": "uuid"},
32
+ fields={
33
+ "id": {"type": "uuid", "required": True},
34
+ "status": {"type": "string", "required": True},
35
+ },
36
+ )
37
+
38
+ assert result == {"table": "jobs", "revision": 1, "scope": "self"}
39
+ assert calls == [
40
+ {
41
+ "endpoint": "/svc/stash/tables/define",
42
+ "method": "POST",
43
+ "body": {
44
+ "env": "production",
45
+ "scope": "self",
46
+ "table": "jobs",
47
+ "if_revision": 0,
48
+ "primary_key": {"field": "id", "generate": "uuid"},
49
+ "fields": {
50
+ "id": {"type": "uuid", "required": True},
51
+ "status": {"type": "string", "required": True},
52
+ },
53
+ },
54
+ "use_gateway": True,
55
+ }
56
+ ]
57
+
58
+
59
+ @pytest.mark.asyncio
60
+ async def test_put_row_uses_row_revision_cas(monkeypatch, sdk):
61
+ calls = []
62
+
63
+ async def fake_request(**kwargs):
64
+ calls.append(kwargs)
65
+ return {"row_key": "job-1", "revision": 3, "schema_revision": 2}
66
+
67
+ monkeypatch.setattr(sdk, "_request", fake_request)
68
+
69
+ result = await sdk.stash.tables.put_row(
70
+ env="staging",
71
+ scope="group",
72
+ table="jobs",
73
+ if_revision=2,
74
+ row={"id": "job-1", "status": "ready"},
75
+ )
76
+
77
+ assert result["revision"] == 3
78
+ assert calls == [
79
+ {
80
+ "endpoint": "/svc/stash/tables/put-row",
81
+ "method": "POST",
82
+ "body": {
83
+ "env": "staging",
84
+ "scope": "group",
85
+ "table": "jobs",
86
+ "if_revision": 2,
87
+ "row": {"id": "job-1", "status": "ready"},
88
+ },
89
+ "use_gateway": True,
90
+ }
91
+ ]
92
+
93
+
94
+ @pytest.mark.asyncio
95
+ async def test_get_row_posts_integer_key_without_revision(monkeypatch, sdk):
96
+ calls = []
97
+
98
+ async def fake_request(**kwargs):
99
+ calls.append(kwargs)
100
+ return {"row_key": "42", "data": {"id": 42}}
101
+
102
+ monkeypatch.setattr(sdk, "_request", fake_request)
103
+
104
+ result = await sdk.stash.tables.get_row(
105
+ env="development",
106
+ scope="self",
107
+ table="jobs",
108
+ key=42,
109
+ )
110
+
111
+ assert result["row_key"] == "42"
112
+ assert calls == [
113
+ {
114
+ "endpoint": "/svc/stash/tables/get-row",
115
+ "method": "POST",
116
+ "body": {
117
+ "env": "development",
118
+ "scope": "self",
119
+ "table": "jobs",
120
+ "key": 42,
121
+ },
122
+ "use_gateway": True,
123
+ }
124
+ ]
125
+
126
+
127
+ @pytest.mark.asyncio
128
+ async def test_select_omits_optional_query_fields_by_default(monkeypatch, sdk):
129
+ calls = []
130
+
131
+ async def fake_request(**kwargs):
132
+ calls.append(kwargs)
133
+ return {"rows": [], "next_cursor": None, "spec_revision": 1}
134
+
135
+ monkeypatch.setattr(sdk, "_request", fake_request)
136
+
137
+ result = await sdk.stash.tables.select(
138
+ env="production",
139
+ scope="self",
140
+ table="jobs",
141
+ )
142
+
143
+ assert result["next_cursor"] is None
144
+ assert calls == [
145
+ {
146
+ "endpoint": "/svc/stash/tables/select",
147
+ "method": "POST",
148
+ "body": {
149
+ "env": "production",
150
+ "scope": "self",
151
+ "table": "jobs",
152
+ },
153
+ "use_gateway": True,
154
+ }
155
+ ]
156
+
157
+
158
+ @pytest.mark.asyncio
159
+ async def test_select_passes_shallow_boolean_filter_and_opaque_cursor(monkeypatch, sdk):
160
+ calls = []
161
+
162
+ async def fake_request(**kwargs):
163
+ calls.append(kwargs)
164
+ return {"rows": [], "next_cursor": "next-token", "spec_revision": 4}
165
+
166
+ monkeypatch.setattr(sdk, "_request", fake_request)
167
+ where = {
168
+ "or": [
169
+ {"field": "status", "op": "eq", "value": "ready"},
170
+ {
171
+ "and": [
172
+ {"field": "priority", "op": "gte", "value": 5},
173
+ {"field": "enabled", "op": "eq", "value": True},
174
+ ]
175
+ },
176
+ ]
177
+ }
178
+
179
+ result = await sdk.stash.tables.select(
180
+ env="production",
181
+ scope="group",
182
+ table="jobs",
183
+ where=where,
184
+ order_by={"field": "priority", "direction": "desc"},
185
+ page_size=25,
186
+ cursor="opaque-token",
187
+ )
188
+
189
+ assert result["next_cursor"] == "next-token"
190
+ assert calls == [
191
+ {
192
+ "endpoint": "/svc/stash/tables/select",
193
+ "method": "POST",
194
+ "body": {
195
+ "env": "production",
196
+ "scope": "group",
197
+ "table": "jobs",
198
+ "where": where,
199
+ "order_by": {"field": "priority", "direction": "desc"},
200
+ "page_size": 25,
201
+ "cursor": "opaque-token",
202
+ },
203
+ "use_gateway": True,
204
+ }
205
+ ]
206
+
207
+
208
+ @pytest.mark.asyncio
209
+ async def test_update_where_uses_spec_revision_and_guard_limit(monkeypatch, sdk):
210
+ calls = []
211
+
212
+ async def fake_request(**kwargs):
213
+ calls.append(kwargs)
214
+ return {
215
+ "matched_count": 1,
216
+ "affected_count": 1,
217
+ "rows": [{"row_key": "job-1", "revision": 2, "schema_revision": 4}],
218
+ "receipt": {"mutation_id": "mutation-1", "created_at": "2026-07-15T12:00:00.000Z"},
219
+ }
220
+
221
+ monkeypatch.setattr(sdk, "_request", fake_request)
222
+ where = [{"field": "status", "op": "eq", "value": "queued"}]
223
+
224
+ result = await sdk.stash.tables.update_where(
225
+ env="production",
226
+ scope="group",
227
+ table="jobs",
228
+ where=where,
229
+ set={"status": "ready"},
230
+ remove=["note"],
231
+ if_spec_revision=4,
232
+ max_affected=10,
233
+ )
234
+
235
+ assert result["receipt"]["mutation_id"] == "mutation-1"
236
+ assert calls == [
237
+ {
238
+ "endpoint": "/svc/stash/tables/update-where",
239
+ "method": "POST",
240
+ "body": {
241
+ "env": "production",
242
+ "scope": "group",
243
+ "table": "jobs",
244
+ "where": where,
245
+ "set": {"status": "ready"},
246
+ "remove": ["note"],
247
+ "if_spec_revision": 4,
248
+ "max_affected": 10,
249
+ },
250
+ "use_gateway": True,
251
+ }
252
+ ]
253
+
254
+
255
+ @pytest.mark.asyncio
256
+ async def test_delete_row_uses_row_revision_and_returns_hard_delete_receipt(monkeypatch, sdk):
257
+ calls = []
258
+
259
+ async def fake_request(**kwargs):
260
+ calls.append(kwargs)
261
+ return {
262
+ "deleted": True,
263
+ "row_key": "job-1",
264
+ "revision": 3,
265
+ "schema_revision": 4,
266
+ "receipt": {"mutation_id": "delete-1", "created_at": "2026-07-15T12:00:00.000Z"},
267
+ }
268
+
269
+ monkeypatch.setattr(sdk, "_request", fake_request)
270
+
271
+ result = await sdk.stash.tables.delete_row(
272
+ env="production",
273
+ scope="self",
274
+ table="jobs",
275
+ key="job-1",
276
+ if_revision=3,
277
+ )
278
+
279
+ assert result["deleted"] is True
280
+ assert result["receipt"]["mutation_id"] == "delete-1"
281
+ assert calls == [
282
+ {
283
+ "endpoint": "/svc/stash/tables/delete-row",
284
+ "method": "POST",
285
+ "body": {
286
+ "env": "production",
287
+ "scope": "self",
288
+ "table": "jobs",
289
+ "key": "job-1",
290
+ "if_revision": 3,
291
+ },
292
+ "use_gateway": True,
293
+ }
294
+ ]