dominus-sdk-python 9.0.14__tar.gz → 9.0.16__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 (88) hide show
  1. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/PKG-INFO +5 -4
  2. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/README.md +4 -3
  3. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/__init__.py +1 -1
  4. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/helpers/cache.py +6 -6
  5. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/namespaces/secrets.py +16 -6
  6. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus_sdk_python.egg-info/PKG-INFO +5 -4
  7. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus_sdk_python.egg-info/SOURCES.txt +1 -0
  8. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/pyproject.toml +1 -1
  9. dominus_sdk_python-9.0.16/tests/test_cache_contracts.py +115 -0
  10. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/tests/test_secrets_presence_contract.py +36 -0
  11. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/config/__init__.py +0 -0
  12. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/config/endpoints.py +0 -0
  13. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/contract/__init__.py +0 -0
  14. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/contract/envelope.py +0 -0
  15. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/contract/projections.py +0 -0
  16. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/errors.py +0 -0
  17. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/helpers/__init__.py +0 -0
  18. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/helpers/auth.py +0 -0
  19. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/helpers/console_capture.py +0 -0
  20. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/helpers/core.py +0 -0
  21. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/helpers/crypto.py +0 -0
  22. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/helpers/sse.py +0 -0
  23. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/helpers/trace.py +0 -0
  24. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/namespaces/__init__.py +0 -0
  25. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/namespaces/admin.py +0 -0
  26. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/namespaces/ai.py +0 -0
  27. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/namespaces/artifacts.py +0 -0
  28. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/namespaces/auth.py +0 -0
  29. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/namespaces/authority.py +0 -0
  30. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/namespaces/browser.py +0 -0
  31. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/namespaces/coder.py +0 -0
  32. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/namespaces/courier.py +0 -0
  33. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/namespaces/db.py +0 -0
  34. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/namespaces/ddl.py +0 -0
  35. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/namespaces/deployer.py +0 -0
  36. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/namespaces/fastapi.py +0 -0
  37. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/namespaces/files.py +0 -0
  38. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/namespaces/health.py +0 -0
  39. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/namespaces/jobs.py +0 -0
  40. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/namespaces/logs.py +0 -0
  41. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/namespaces/platform.py +0 -0
  42. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/namespaces/portal.py +0 -0
  43. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/namespaces/processor.py +0 -0
  44. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/namespaces/publisher.py +0 -0
  45. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/namespaces/recipes.py +0 -0
  46. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/namespaces/redis.py +0 -0
  47. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/namespaces/secure.py +0 -0
  48. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/namespaces/stash.py +0 -0
  49. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/namespaces/sync.py +0 -0
  50. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/namespaces/usage.py +0 -0
  51. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/namespaces/warden.py +0 -0
  52. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/namespaces/workflow.py +0 -0
  53. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/py.typed +0 -0
  54. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/services/__init__.py +0 -0
  55. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus/start.py +0 -0
  56. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus_sdk_python.egg-info/dependency_links.txt +0 -0
  57. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus_sdk_python.egg-info/requires.txt +0 -0
  58. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/dominus_sdk_python.egg-info/top_level.txt +0 -0
  59. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/setup.cfg +0 -0
  60. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/tests/test_ai_namespace.py +0 -0
  61. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/tests/test_artifacts_namespace.py +0 -0
  62. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/tests/test_auth.py +0 -0
  63. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/tests/test_authority_public_vocabulary.py +0 -0
  64. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/tests/test_browser_namespace.py +0 -0
  65. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/tests/test_contract.py +0 -0
  66. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/tests/test_control_plane_namespaces.py +0 -0
  67. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/tests/test_error_recovery_action.py +0 -0
  68. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/tests/test_errors.py +0 -0
  69. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/tests/test_errors_aliases.py +0 -0
  70. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/tests/test_flat_commands.py +0 -0
  71. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/tests/test_health.py +0 -0
  72. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/tests/test_logs.py +0 -0
  73. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/tests/test_namespace_barrel_exports.py +0 -0
  74. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/tests/test_platform_coder_namespaces.py +0 -0
  75. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/tests/test_provisioning_parity.py +0 -0
  76. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/tests/test_public_exports.py +0 -0
  77. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/tests/test_publisher_namespace.py +0 -0
  78. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/tests/test_readme_quickstart_catalog.py +0 -0
  79. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/tests/test_recipes_namespace.py +0 -0
  80. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/tests/test_recipes_stash_routing.py +0 -0
  81. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/tests/test_stash_artifact_facade.py +0 -0
  82. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/tests/test_stash_tables_namespace.py +0 -0
  83. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/tests/test_token_precedence.py +0 -0
  84. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/tests/test_transport_compat.py +0 -0
  85. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/tests/test_usage_namespace.py +0 -0
  86. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/tests/test_version_consistency.py +0 -0
  87. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/tests/test_workflow_lifecycle.py +0 -0
  88. {dominus_sdk_python-9.0.14 → dominus_sdk_python-9.0.16}/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: 9.0.14
3
+ Version: 9.0.16
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
@@ -38,8 +38,9 @@ Async Python SDK for the Dominus gateway-first service plane.
38
38
  ## Agent Guide
39
39
 
40
40
  Start with `docs/agent-guide/INDEX.md`. The current snapshot is
41
- `docs/agent-guide/2026-09-19-1300-sdk-python/00-reading-order.md`; the latest
42
- cleanup audit is `docs/janitor/2026-09-19-1300-sdk-python-cleanup-audit.md`.
41
+ `docs/agent-guide/2026-10-16-1300-sdk-python/00-reading-order.md`; the latest
42
+ cleanup audit is `docs/janitor/2026-10-16-1300-sdk-python-cleanup-audit.md`.
43
+ Knowledge graph: graphify-out/GRAPH_REPORT.md — query with `graphify-run query "<question>"` before grepping.
43
44
 
44
45
  ## What This Repo Ships
45
46
 
@@ -48,7 +49,7 @@ cleanup audit is `docs/janitor/2026-09-19-1300-sdk-python-cleanup-audit.md`.
48
49
  - Gateway-scoped client mode for MCP and other user-JWT sessions
49
50
  - Transport compatibility for wrapped `{success,data}` responses and unwrapped Warden/control-plane success objects
50
51
  - Local helpers for JWT verification, trace propagation, retries, and console capture
51
- - Current package version: `9.0.14`
52
+ - Current package version: `9.0.16`
52
53
 
53
54
  ## Install
54
55
 
@@ -5,8 +5,9 @@ Async Python SDK for the Dominus gateway-first service plane.
5
5
  ## Agent Guide
6
6
 
7
7
  Start with `docs/agent-guide/INDEX.md`. The current snapshot is
8
- `docs/agent-guide/2026-09-19-1300-sdk-python/00-reading-order.md`; the latest
9
- cleanup audit is `docs/janitor/2026-09-19-1300-sdk-python-cleanup-audit.md`.
8
+ `docs/agent-guide/2026-10-16-1300-sdk-python/00-reading-order.md`; the latest
9
+ cleanup audit is `docs/janitor/2026-10-16-1300-sdk-python-cleanup-audit.md`.
10
+ Knowledge graph: graphify-out/GRAPH_REPORT.md — query with `graphify-run query "<question>"` before grepping.
10
11
 
11
12
  ## What This Repo Ships
12
13
 
@@ -15,7 +16,7 @@ cleanup audit is `docs/janitor/2026-09-19-1300-sdk-python-cleanup-audit.md`.
15
16
  - Gateway-scoped client mode for MCP and other user-JWT sessions
16
17
  - Transport compatibility for wrapped `{success,data}` responses and unwrapped Warden/control-plane success objects
17
18
  - Local helpers for JWT verification, trace propagation, retries, and console capture
18
- - Current package version: `9.0.14`
19
+ - Current package version: `9.0.16`
19
20
 
20
21
  ## Install
21
22
 
@@ -191,7 +191,7 @@ from .errors import (
191
191
  TimeoutError as DominusTimeoutError,
192
192
  )
193
193
 
194
- __version__ = "9.0.14"
194
+ __version__ = "9.0.16"
195
195
  __all__ = [
196
196
  # Main SDK instance
197
197
  "dominus",
@@ -108,7 +108,7 @@ def exponential_backoff_with_jitter(
108
108
  """
109
109
  delay = min(base_delay * (2 ** attempt), max_delay)
110
110
  jitter_range = delay * jitter
111
- return delay + random.uniform(-jitter_range, jitter_range)
111
+ return min(max_delay, delay + random.uniform(-jitter_range, jitter_range))
112
112
 
113
113
 
114
114
  class DominusCache:
@@ -124,7 +124,7 @@ class DominusCache:
124
124
  """
125
125
 
126
126
  def __init__(self, default_ttl: int = 300):
127
- self._store: Dict[str, Tuple[bytes, float]] = {}
127
+ self._store: Dict[str, Tuple[bytes, float, int]] = {}
128
128
  self._default_ttl = default_ttl
129
129
  self._cipher: Optional[Fernet] = None
130
130
 
@@ -141,7 +141,7 @@ class DominusCache:
141
141
  if not entry:
142
142
  return None
143
143
 
144
- encrypted_value, expires_at = entry
144
+ encrypted_value, expires_at, duration = entry
145
145
 
146
146
  # Check expiry
147
147
  if time.time() >= expires_at:
@@ -159,8 +159,8 @@ class DominusCache:
159
159
  else:
160
160
  value = json.loads(encrypted_value.decode())
161
161
 
162
- # Touch TTL
163
- self._store[key] = (encrypted_value, time.time() + self._default_ttl)
162
+ # Touch TTL using the entry's own duration
163
+ self._store[key] = (encrypted_value, time.time() + duration, duration)
164
164
  return value
165
165
 
166
166
  def set(self, key: str, value: Any, ttl: Optional[int] = None) -> None:
@@ -173,7 +173,7 @@ class DominusCache:
173
173
  else:
174
174
  encrypted_value = json.dumps(value).encode()
175
175
 
176
- self._store[key] = (encrypted_value, time.time() + duration)
176
+ self._store[key] = (encrypted_value, time.time() + duration, duration)
177
177
 
178
178
  def delete(self, key: str) -> bool:
179
179
  """Delete key."""
@@ -262,15 +262,22 @@ class SecretsNamespace:
262
262
  environment: str,
263
263
  project_id: str,
264
264
  ) -> Dict[str, Any]:
265
- """Set one Infisical secret for an environment (``POST /api/secrets/set``)."""
265
+ """Set one Infisical secret for an environment (``POST /api/secrets/set``).
266
+
267
+ The wire keys are the canonical ``target_org_id`` / ``target_env``
268
+ (PLAN T6 / board T7); secrets-core rejects the retired
269
+ ``target_project_id`` / ``environment`` aliases with a typed
270
+ ``invalid_wire_key`` validation error. The Python parameter names are
271
+ unchanged — only the request body vocabulary is canonical.
272
+ """
266
273
  return await self._client._request(
267
274
  endpoint="/api/secrets/set",
268
275
  method="POST",
269
276
  body={
270
277
  "key": key,
271
278
  "value": value,
272
- "environment": environment,
273
- "target_project_id": project_id,
279
+ "target_env": environment,
280
+ "target_org_id": project_id,
274
281
  },
275
282
  use_gateway=True,
276
283
  )
@@ -282,14 +289,17 @@ class SecretsNamespace:
282
289
  environment: str,
283
290
  project_id: str,
284
291
  ) -> Dict[str, Any]:
285
- """Delete one Infisical secret for an environment (``POST /api/secrets/delete``)."""
292
+ """Delete one Infisical secret for an environment (``POST /api/secrets/delete``).
293
+
294
+ Canonical wire keys only — see :meth:`set_project_secret`.
295
+ """
286
296
  return await self._client._request(
287
297
  endpoint="/api/secrets/delete",
288
298
  method="POST",
289
299
  body={
290
300
  "key": key,
291
- "environment": environment,
292
- "target_project_id": project_id,
301
+ "target_env": environment,
302
+ "target_org_id": project_id,
293
303
  },
294
304
  use_gateway=True,
295
305
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dominus-sdk-python
3
- Version: 9.0.14
3
+ Version: 9.0.16
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
@@ -38,8 +38,9 @@ Async Python SDK for the Dominus gateway-first service plane.
38
38
  ## Agent Guide
39
39
 
40
40
  Start with `docs/agent-guide/INDEX.md`. The current snapshot is
41
- `docs/agent-guide/2026-09-19-1300-sdk-python/00-reading-order.md`; the latest
42
- cleanup audit is `docs/janitor/2026-09-19-1300-sdk-python-cleanup-audit.md`.
41
+ `docs/agent-guide/2026-10-16-1300-sdk-python/00-reading-order.md`; the latest
42
+ cleanup audit is `docs/janitor/2026-10-16-1300-sdk-python-cleanup-audit.md`.
43
+ Knowledge graph: graphify-out/GRAPH_REPORT.md — query with `graphify-run query "<question>"` before grepping.
43
44
 
44
45
  ## What This Repo Ships
45
46
 
@@ -48,7 +49,7 @@ cleanup audit is `docs/janitor/2026-09-19-1300-sdk-python-cleanup-audit.md`.
48
49
  - Gateway-scoped client mode for MCP and other user-JWT sessions
49
50
  - Transport compatibility for wrapped `{success,data}` responses and unwrapped Warden/control-plane success objects
50
51
  - Local helpers for JWT verification, trace propagation, retries, and console capture
51
- - Current package version: `9.0.14`
52
+ - Current package version: `9.0.16`
52
53
 
53
54
  ## Install
54
55
 
@@ -58,6 +58,7 @@ tests/test_artifacts_namespace.py
58
58
  tests/test_auth.py
59
59
  tests/test_authority_public_vocabulary.py
60
60
  tests/test_browser_namespace.py
61
+ tests/test_cache_contracts.py
61
62
  tests/test_contract.py
62
63
  tests/test_control_plane_namespaces.py
63
64
  tests/test_error_recovery_action.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "dominus-sdk-python"
7
- version = "9.0.14"
7
+ version = "9.0.16"
8
8
  description = "Python SDK for the Dominus gateway-first platform"
9
9
  readme = "README.md"
10
10
  license = "LicenseRef-Proprietary"
@@ -0,0 +1,115 @@
1
+ """Regression contracts for dominus.helpers.cache.
2
+
3
+ Pins two documented behaviours that the implementation previously broke:
4
+
5
+ 1. ``exponential_backoff_with_jitter`` must never return a delay above the
6
+ ``max_delay`` cap, including when the exponential term already equals the
7
+ cap and positive jitter would otherwise push it over.
8
+ 2. ``DominusCache.get`` refreshes an entry using the TTL that ``set`` was
9
+ given (or ``default_ttl`` when none was supplied), never silently swapping
10
+ a short per-entry TTL for the cache default.
11
+
12
+ Randomness is seeded so the backoff assertions are fully deterministic.
13
+ """
14
+ import random
15
+
16
+ import pytest
17
+
18
+ import dominus.helpers.cache as cache_module
19
+ from dominus.helpers.cache import (
20
+ DominusCache,
21
+ exponential_backoff_with_jitter,
22
+ )
23
+
24
+
25
+ class _Clock:
26
+ """Deterministic, advanceable replacement for ``time.time``."""
27
+
28
+ def __init__(self, start: float = 1000.0):
29
+ self.now = start
30
+
31
+ def __call__(self) -> float:
32
+ return self.now
33
+
34
+
35
+ def test_backoff_never_exceeds_max_delay_across_attempts():
36
+ random.seed(1234)
37
+
38
+ samples = [
39
+ exponential_backoff_with_jitter(attempt, base_delay=1.0, max_delay=8.0, jitter=0.5)
40
+ for attempt in range(0, 8)
41
+ for _ in range(200)
42
+ ]
43
+
44
+ assert samples, "expected backoff samples"
45
+ assert all(delay <= 8.0 for delay in samples)
46
+ assert all(delay >= 0.0 for delay in samples)
47
+
48
+
49
+ def test_backoff_caps_at_boundary_where_exponential_equals_max_delay():
50
+ # base_delay * 2 ** attempt == max_delay exactly at attempt 3 (1.0 * 8 == 8.0).
51
+ random.seed(7)
52
+
53
+ samples = [
54
+ exponential_backoff_with_jitter(3, base_delay=1.0, max_delay=8.0, jitter=0.5)
55
+ for _ in range(500)
56
+ ]
57
+
58
+ assert max(samples) <= 8.0
59
+ # Jitter must still be applied below the cap: some draws land under it.
60
+ assert min(samples) < 8.0
61
+
62
+
63
+ def test_backoff_preserves_growth_and_jitter_below_cap():
64
+ # attempt 2 -> exponential term 4.0, strictly below the 8.0 cap, so the
65
+ # +/-50% jitter band must be intact and unclamped.
66
+ random.seed(99)
67
+
68
+ samples = [
69
+ exponential_backoff_with_jitter(2, base_delay=1.0, max_delay=8.0, jitter=0.5)
70
+ for _ in range(300)
71
+ ]
72
+
73
+ assert all(2.0 <= delay <= 6.0 for delay in samples)
74
+ assert any(delay < 4.0 for delay in samples)
75
+ assert any(delay > 4.0 for delay in samples)
76
+
77
+
78
+ def test_get_refresh_preserves_explicit_per_entry_ttl(monkeypatch):
79
+ clock = _Clock()
80
+ monkeypatch.setattr(cache_module.time, "time", clock)
81
+
82
+ cache = DominusCache(default_ttl=300)
83
+ cache.set("short", "value", ttl=10)
84
+
85
+ clock.now += 5
86
+ assert cache.get("short") == "value" # refresh must reuse the 10s TTL
87
+
88
+ clock.now += 11 # 1016s > refreshed expiry 1015s (5 + 10)
89
+ assert cache.get("short") is None
90
+
91
+
92
+ def test_get_refresh_uses_default_ttl_when_entry_has_none(monkeypatch):
93
+ clock = _Clock()
94
+ monkeypatch.setattr(cache_module.time, "time", clock)
95
+
96
+ cache = DominusCache(default_ttl=300)
97
+ cache.set("default", "value")
98
+
99
+ clock.now += 100
100
+ assert cache.get("default") == "value" # refresh to now + 300s
101
+
102
+ clock.now += 301 # 1401s > refreshed expiry 1400s (1100 + 300)
103
+ assert cache.get("default") is None
104
+
105
+
106
+ def test_encrypted_cache_round_trips():
107
+ cache = DominusCache(default_ttl=300)
108
+ cache.set_encryption_key("super-secret-token")
109
+
110
+ assert cache._cipher is not None
111
+
112
+ payload = {"nested": {"a": 1, "b": [1, 2, 3]}, "text": "value"}
113
+ cache.set("encrypted", payload, ttl=30)
114
+
115
+ assert cache.get("encrypted") == payload
@@ -120,6 +120,42 @@ async def test_malformed_responses_fail_without_raw_cell_in_error_or_logs(caplog
120
120
  assert RAW_SENTINEL not in caplog.text
121
121
 
122
122
 
123
+ @pytest.mark.asyncio
124
+ async def test_project_secret_mutations_speak_the_canonical_wire_vocabulary():
125
+ """T7 contracts the secrets request surface onto target_org_id/target_env.
126
+
127
+ dominus-secrets-core rejects the retired `target_project_id` / `environment`
128
+ aliases with a typed `invalid_wire_key` validation error instead of
129
+ silently ignoring them, so emitting either key here would 400 the call.
130
+ The Python method signatures keep their own parameter names; only the wire
131
+ body keys are canonical.
132
+ """
133
+ client = Client({"ok": True})
134
+ ns = SecretsNamespace(client)
135
+
136
+ await ns.set_project_secret(key="K", value="V", environment="production", project_id="org-1")
137
+ await ns.delete_project_secret(key="K", environment="production", project_id="org-1")
138
+
139
+ assert client.calls == [
140
+ {
141
+ "endpoint": "/api/secrets/set",
142
+ "method": "POST",
143
+ "body": {"key": "K", "value": "V", "target_env": "production", "target_org_id": "org-1"},
144
+ "use_gateway": True,
145
+ },
146
+ {
147
+ "endpoint": "/api/secrets/delete",
148
+ "method": "POST",
149
+ "body": {"key": "K", "target_env": "production", "target_org_id": "org-1"},
150
+ "use_gateway": True,
151
+ },
152
+ ]
153
+
154
+ for call in client.calls:
155
+ assert "target_project_id" not in call["body"]
156
+ assert "environment" not in call["body"]
157
+
158
+
123
159
  def test_public_types_and_return_annotation_are_exact():
124
160
  from dominus import SecretEnvironment, SecretPresenceCell, SecretPresenceGrid
125
161
  from dominus.namespaces import (