dominus-sdk-python 4.0.0__tar.gz → 4.0.2__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 (58) hide show
  1. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/PKG-INFO +19 -2
  2. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/README.md +18 -1
  3. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/dominus/namespaces/authority.py +60 -0
  4. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/dominus/namespaces/logs.py +84 -0
  5. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/dominus/start.py +11 -3
  6. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/dominus_sdk_python.egg-info/PKG-INFO +19 -2
  7. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/pyproject.toml +1 -1
  8. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/tests/test_authority_public_vocabulary.py +43 -0
  9. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/tests/test_logs.py +57 -1
  10. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/tests/test_transport_compat.py +42 -0
  11. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/dominus/__init__.py +0 -0
  12. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/dominus/config/__init__.py +0 -0
  13. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/dominus/config/endpoints.py +0 -0
  14. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/dominus/errors.py +0 -0
  15. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/dominus/helpers/__init__.py +0 -0
  16. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/dominus/helpers/auth.py +0 -0
  17. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/dominus/helpers/cache.py +0 -0
  18. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/dominus/helpers/console_capture.py +0 -0
  19. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/dominus/helpers/core.py +0 -0
  20. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/dominus/helpers/crypto.py +0 -0
  21. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/dominus/helpers/sse.py +0 -0
  22. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/dominus/helpers/trace.py +0 -0
  23. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/dominus/namespaces/__init__.py +0 -0
  24. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/dominus/namespaces/admin.py +0 -0
  25. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/dominus/namespaces/ai.py +0 -0
  26. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/dominus/namespaces/artifacts.py +0 -0
  27. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/dominus/namespaces/auth.py +0 -0
  28. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/dominus/namespaces/courier.py +0 -0
  29. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/dominus/namespaces/db.py +0 -0
  30. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/dominus/namespaces/ddl.py +0 -0
  31. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/dominus/namespaces/deployer.py +0 -0
  32. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/dominus/namespaces/fastapi.py +0 -0
  33. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/dominus/namespaces/files.py +0 -0
  34. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/dominus/namespaces/health.py +0 -0
  35. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/dominus/namespaces/jobs.py +0 -0
  36. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/dominus/namespaces/portal.py +0 -0
  37. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/dominus/namespaces/processor.py +0 -0
  38. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/dominus/namespaces/redis.py +0 -0
  39. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/dominus/namespaces/secrets.py +0 -0
  40. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/dominus/namespaces/secure.py +0 -0
  41. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/dominus/namespaces/sync.py +0 -0
  42. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/dominus/namespaces/warden.py +0 -0
  43. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/dominus/namespaces/workflow.py +0 -0
  44. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/dominus/services/__init__.py +0 -0
  45. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/dominus_sdk_python.egg-info/SOURCES.txt +0 -0
  46. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/dominus_sdk_python.egg-info/dependency_links.txt +0 -0
  47. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/dominus_sdk_python.egg-info/requires.txt +0 -0
  48. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/dominus_sdk_python.egg-info/top_level.txt +0 -0
  49. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/setup.cfg +0 -0
  50. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/tests/test_auth.py +0 -0
  51. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/tests/test_control_plane_namespaces.py +0 -0
  52. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/tests/test_errors.py +0 -0
  53. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/tests/test_flat_commands.py +0 -0
  54. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/tests/test_health.py +0 -0
  55. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/tests/test_provisioning_parity.py +0 -0
  56. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/tests/test_public_exports.py +0 -0
  57. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/tests/test_workflow_lifecycle.py +0 -0
  58. {dominus_sdk_python-4.0.0 → dominus_sdk_python-4.0.2}/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.0
3
+ Version: 4.0.2
4
4
  Summary: Python SDK for the Dominus gateway-first platform
5
5
  Author-email: CareBridge Systems <dev@carebridge.io>
6
6
  License: Proprietary
@@ -40,8 +40,9 @@ Async Python SDK for the Dominus gateway-first service plane.
40
40
  - Python 3.9+ asyncio client for Dominus services
41
41
  - Namespace-first API with a small root shortcut surface
42
42
  - Gateway-scoped client mode for MCP and other user-JWT sessions
43
+ - Transport compatibility for wrapped `{success,data}` responses and unwrapped Warden/control-plane success objects
43
44
  - Local helpers for JWT verification, trace propagation, retries, and console capture
44
- - Current package version: `3.0.5`
45
+ - Current package version: `4.0.2`
45
46
 
46
47
  ## Install
47
48
 
@@ -74,6 +75,22 @@ timeline = await dominus.authority.get_run_timeline(
74
75
  since="2026-04-11T08:33:00Z",
75
76
  until="2026-04-11T09:33:00Z",
76
77
  )
78
+
79
+ timeline_archive = await dominus.authority.get_timeline_archive_status(
80
+ app_slug="carebridge-summit",
81
+ env="production",
82
+ )
83
+ logs_archive = await dominus.logs.get_archive_status(all_scopes=True, limit=5)
84
+
85
+ # Archive maintenance is explicit and dry-run first.
86
+ verify = await dominus.authority.verify_timeline_archive_manifests(
87
+ since="2026-04-01T00:00:00Z",
88
+ until="2026-04-02T00:00:00Z",
89
+ )
90
+ prune = await dominus.logs.prune_archive_retention(
91
+ retention_days=90,
92
+ dry_run=True,
93
+ )
77
94
  ```
78
95
 
79
96
  ## Session-Scoped Clients
@@ -7,8 +7,9 @@ Async Python SDK for the Dominus gateway-first service plane.
7
7
  - Python 3.9+ asyncio client for Dominus services
8
8
  - Namespace-first API with a small root shortcut surface
9
9
  - Gateway-scoped client mode for MCP and other user-JWT sessions
10
+ - Transport compatibility for wrapped `{success,data}` responses and unwrapped Warden/control-plane success objects
10
11
  - Local helpers for JWT verification, trace propagation, retries, and console capture
11
- - Current package version: `3.0.5`
12
+ - Current package version: `4.0.2`
12
13
 
13
14
  ## Install
14
15
 
@@ -41,6 +42,22 @@ timeline = await dominus.authority.get_run_timeline(
41
42
  since="2026-04-11T08:33:00Z",
42
43
  until="2026-04-11T09:33:00Z",
43
44
  )
45
+
46
+ timeline_archive = await dominus.authority.get_timeline_archive_status(
47
+ app_slug="carebridge-summit",
48
+ env="production",
49
+ )
50
+ logs_archive = await dominus.logs.get_archive_status(all_scopes=True, limit=5)
51
+
52
+ # Archive maintenance is explicit and dry-run first.
53
+ verify = await dominus.authority.verify_timeline_archive_manifests(
54
+ since="2026-04-01T00:00:00Z",
55
+ until="2026-04-02T00:00:00Z",
56
+ )
57
+ prune = await dominus.logs.prune_archive_retention(
58
+ retention_days=90,
59
+ dry_run=True,
60
+ )
44
61
  ```
45
62
 
46
63
  ## Session-Scoped Clients
@@ -1574,6 +1574,66 @@ class AuthorityNamespace:
1574
1574
  timeout=self._http_timeout(timeout, 15.0),
1575
1575
  )
1576
1576
 
1577
+ async def repair_timeline_archive_manifests(
1578
+ self,
1579
+ *,
1580
+ since: Optional[str] = None,
1581
+ until: Optional[str] = None,
1582
+ timeout: Optional[float] = None,
1583
+ ) -> Dict[str, Any]:
1584
+ """Rebuild Authority timeline daily archive manifests from cold buckets. ``POST /api/authority/timelines/archive-repair``."""
1585
+ body = _compact({
1586
+ "since": since,
1587
+ "until": until,
1588
+ })
1589
+ return await self._post(
1590
+ "/api/authority/timelines/archive-repair",
1591
+ body,
1592
+ timeout=self._http_timeout(timeout, 30.0),
1593
+ )
1594
+
1595
+ async def verify_timeline_archive_manifests(
1596
+ self,
1597
+ *,
1598
+ since: Optional[str] = None,
1599
+ until: Optional[str] = None,
1600
+ timeout: Optional[float] = None,
1601
+ ) -> Dict[str, Any]:
1602
+ """Verify Authority timeline daily archive manifests against cold buckets. ``POST /api/authority/timelines/archive-verify``."""
1603
+ body = _compact({
1604
+ "since": since,
1605
+ "until": until,
1606
+ })
1607
+ return await self._post(
1608
+ "/api/authority/timelines/archive-verify",
1609
+ body,
1610
+ timeout=self._http_timeout(timeout, 30.0),
1611
+ )
1612
+
1613
+ async def prune_timeline_archive_retention(
1614
+ self,
1615
+ *,
1616
+ since: Optional[str] = None,
1617
+ until: Optional[str] = None,
1618
+ retention_days: Optional[int] = None,
1619
+ dry_run: bool = True,
1620
+ confirm: Optional[str] = None,
1621
+ timeout: Optional[float] = None,
1622
+ ) -> Dict[str, Any]:
1623
+ """Dry-run or enforce Authority timeline cold archive retention. ``POST /api/authority/timelines/archive-prune``."""
1624
+ body = _compact({
1625
+ "since": since,
1626
+ "until": until,
1627
+ "retention_days": retention_days,
1628
+ "dry_run": dry_run,
1629
+ "confirm": confirm,
1630
+ })
1631
+ return await self._post(
1632
+ "/api/authority/timelines/archive-prune",
1633
+ body,
1634
+ timeout=self._http_timeout(timeout, 30.0),
1635
+ )
1636
+
1577
1637
  # ==================================================================
1578
1638
  # Context
1579
1639
  # ==================================================================
@@ -407,6 +407,7 @@ class LogsNamespace:
407
407
  target_org_id: Optional[str] = None,
408
408
  target_env: Optional[str] = None,
409
409
  use_shared: Optional[bool] = None,
410
+ include_buffer: Optional[bool] = None,
410
411
  limit: Optional[int] = None,
411
412
  ) -> Dict[str, Any]:
412
413
  """Get Logs Worker archive backlog state. ``GET /api/logs/archive-status``."""
@@ -419,6 +420,8 @@ class LogsNamespace:
419
420
  params["target_env"] = target_env
420
421
  if use_shared is True:
421
422
  params["use_shared"] = "true"
423
+ if include_buffer is not None:
424
+ params["include_buffer"] = "true" if include_buffer else "false"
422
425
  if limit is not None:
423
426
  params["limit"] = limit
424
427
  qs = urlencode(params)
@@ -429,6 +432,87 @@ class LogsNamespace:
429
432
  use_gateway=True,
430
433
  )
431
434
 
435
+ async def repair_archive_manifests(
436
+ self,
437
+ *,
438
+ since: Optional[str] = None,
439
+ until: Optional[str] = None,
440
+ target_org_id: Optional[str] = None,
441
+ target_env: Optional[str] = None,
442
+ use_shared: Optional[bool] = None,
443
+ ) -> Dict[str, Any]:
444
+ """Rebuild Logs Worker daily archive manifests from existing cold buckets. ``POST /api/logs/archive-repair``."""
445
+ body = {
446
+ "since": since,
447
+ "until": until,
448
+ "target_org_id": target_org_id,
449
+ "target_env": target_env,
450
+ }
451
+ if use_shared is not None:
452
+ body["use_shared"] = use_shared
453
+ return await self._client._request(
454
+ endpoint="/api/logs/archive-repair",
455
+ method="POST",
456
+ body={k: v for k, v in body.items() if v is not None and v != ""},
457
+ use_gateway=True,
458
+ )
459
+
460
+ async def verify_archive_manifests(
461
+ self,
462
+ *,
463
+ since: Optional[str] = None,
464
+ until: Optional[str] = None,
465
+ target_org_id: Optional[str] = None,
466
+ target_env: Optional[str] = None,
467
+ use_shared: Optional[bool] = None,
468
+ ) -> Dict[str, Any]:
469
+ """Verify Logs Worker daily archive manifests against existing cold buckets. ``POST /api/logs/archive-verify``."""
470
+ body = {
471
+ "since": since,
472
+ "until": until,
473
+ "target_org_id": target_org_id,
474
+ "target_env": target_env,
475
+ }
476
+ if use_shared is not None:
477
+ body["use_shared"] = use_shared
478
+ return await self._client._request(
479
+ endpoint="/api/logs/archive-verify",
480
+ method="POST",
481
+ body={k: v for k, v in body.items() if v is not None and v != ""},
482
+ use_gateway=True,
483
+ )
484
+
485
+ async def prune_archive_retention(
486
+ self,
487
+ *,
488
+ since: Optional[str] = None,
489
+ until: Optional[str] = None,
490
+ retention_days: Optional[int] = None,
491
+ dry_run: bool = True,
492
+ confirm: Optional[str] = None,
493
+ target_org_id: Optional[str] = None,
494
+ target_env: Optional[str] = None,
495
+ use_shared: Optional[bool] = None,
496
+ ) -> Dict[str, Any]:
497
+ """Dry-run or enforce Logs Worker cold archive retention. ``POST /api/logs/archive-prune``."""
498
+ body = {
499
+ "since": since,
500
+ "until": until,
501
+ "retention_days": retention_days,
502
+ "dry_run": dry_run,
503
+ "confirm": confirm,
504
+ "target_org_id": target_org_id,
505
+ "target_env": target_env,
506
+ }
507
+ if use_shared is not None:
508
+ body["use_shared"] = use_shared
509
+ return await self._client._request(
510
+ endpoint="/api/logs/archive-prune",
511
+ method="POST",
512
+ body={k: v for k, v in body.items() if v is not None and v != ""},
513
+ use_gateway=True,
514
+ )
515
+
432
516
  async def query(
433
517
  self,
434
518
  minutes: int = 10,
@@ -468,8 +468,12 @@ class Dominus:
468
468
 
469
469
  result = _decode_json_or_b64_json(response.text)
470
470
 
471
- # Check for success
472
- if not result.get("success"):
471
+ if not isinstance(result, dict):
472
+ return {"result": result}
473
+
474
+ # Kernel routes historically returned {success,data}; some modern
475
+ # Warden control-plane routes return a decoded domain object directly.
476
+ if result.get("success") is False:
473
477
  error_msg = result.get("error") or result.get("message") or "Unknown error"
474
478
  error_details = result.get("details")
475
479
  if not isinstance(error_details, dict):
@@ -479,7 +483,11 @@ class Dominus:
479
483
  error_details[key] = result[key]
480
484
  raise DominusError(str(error_msg), details=error_details, endpoint=endpoint)
481
485
 
482
- return result.get("data", {})
486
+ if result.get("success") is True:
487
+ return result.get("data", {})
488
+ if "data" in result and len(result) <= 3:
489
+ return result.get("data", {})
490
+ return result
483
491
 
484
492
  async def gateway_fetch(
485
493
  self,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dominus-sdk-python
3
- Version: 4.0.0
3
+ Version: 4.0.2
4
4
  Summary: Python SDK for the Dominus gateway-first platform
5
5
  Author-email: CareBridge Systems <dev@carebridge.io>
6
6
  License: Proprietary
@@ -40,8 +40,9 @@ Async Python SDK for the Dominus gateway-first service plane.
40
40
  - Python 3.9+ asyncio client for Dominus services
41
41
  - Namespace-first API with a small root shortcut surface
42
42
  - Gateway-scoped client mode for MCP and other user-JWT sessions
43
+ - Transport compatibility for wrapped `{success,data}` responses and unwrapped Warden/control-plane success objects
43
44
  - Local helpers for JWT verification, trace propagation, retries, and console capture
44
- - Current package version: `3.0.5`
45
+ - Current package version: `4.0.2`
45
46
 
46
47
  ## Install
47
48
 
@@ -74,6 +75,22 @@ timeline = await dominus.authority.get_run_timeline(
74
75
  since="2026-04-11T08:33:00Z",
75
76
  until="2026-04-11T09:33:00Z",
76
77
  )
78
+
79
+ timeline_archive = await dominus.authority.get_timeline_archive_status(
80
+ app_slug="carebridge-summit",
81
+ env="production",
82
+ )
83
+ logs_archive = await dominus.logs.get_archive_status(all_scopes=True, limit=5)
84
+
85
+ # Archive maintenance is explicit and dry-run first.
86
+ verify = await dominus.authority.verify_timeline_archive_manifests(
87
+ since="2026-04-01T00:00:00Z",
88
+ until="2026-04-02T00:00:00Z",
89
+ )
90
+ prune = await dominus.logs.prune_archive_retention(
91
+ retention_days=90,
92
+ dry_run=True,
93
+ )
77
94
  ```
78
95
 
79
96
  ## Session-Scoped Clients
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "dominus-sdk-python"
7
- version = "4.0.0"
7
+ version = "4.0.2"
8
8
  description = "Python SDK for the Dominus gateway-first platform"
9
9
  readme = "README.md"
10
10
  license = {text = "Proprietary"}
@@ -79,6 +79,9 @@ def test_authority_scope_signatures_use_new_public_vocabulary():
79
79
  timeline_sig = inspect.signature(AuthorityNamespace.get_run_timeline)
80
80
  query_timelines_sig = inspect.signature(AuthorityNamespace.query_timelines)
81
81
  archive_status_sig = inspect.signature(AuthorityNamespace.get_timeline_archive_status)
82
+ archive_repair_sig = inspect.signature(AuthorityNamespace.repair_timeline_archive_manifests)
83
+ archive_verify_sig = inspect.signature(AuthorityNamespace.verify_timeline_archive_manifests)
84
+ archive_prune_sig = inspect.signature(AuthorityNamespace.prune_timeline_archive_retention)
82
85
  mint_sig = inspect.signature(core_helpers.mint_selected_scope_jwt)
83
86
 
84
87
  for name in ("app_slug", "env", "target_org_id", "target_env", "run_kind", "target_app_slug", "bootstrap_profile"):
@@ -104,6 +107,12 @@ def test_authority_scope_signatures_use_new_public_vocabulary():
104
107
  assert name in timeline_sig.parameters
105
108
  assert name in query_timelines_sig.parameters
106
109
  assert "all_scopes" in archive_status_sig.parameters
110
+ for name in ("since", "until"):
111
+ assert name in archive_repair_sig.parameters
112
+ assert name in archive_verify_sig.parameters
113
+ assert name in archive_prune_sig.parameters
114
+ for name in ("retention_days", "dry_run", "confirm"):
115
+ assert name in archive_prune_sig.parameters
107
116
 
108
117
  for name in ("target_org_id", "target_app_slug", "target_env"):
109
118
  assert name in mint_sig.parameters
@@ -197,6 +206,21 @@ async def test_authority_scope_methods_use_canonical_context_wire_names():
197
206
  env="production",
198
207
  limit=5,
199
208
  )
209
+ await namespace.repair_timeline_archive_manifests(
210
+ since="2026-04-01T00:00:00Z",
211
+ until="2026-04-02T00:00:00Z",
212
+ )
213
+ await namespace.verify_timeline_archive_manifests(
214
+ since="2026-04-01T00:00:00Z",
215
+ until="2026-04-02T00:00:00Z",
216
+ )
217
+ await namespace.prune_timeline_archive_retention(
218
+ since="2026-01-01T00:00:00Z",
219
+ until="2026-01-02T00:00:00Z",
220
+ retention_days=365,
221
+ dry_run=False,
222
+ confirm="DELETE_EXPIRED_ARCHIVE_OBJECTS",
223
+ )
200
224
 
201
225
  ensure_body = client.calls[0]["body"]
202
226
  assert ensure_body["app_slug"] == "carebridge"
@@ -255,6 +279,25 @@ async def test_authority_scope_methods_use_canonical_context_wire_names():
255
279
  "/api/authority/timelines/archive-status?"
256
280
  "all_scopes=true&app_slug=carebridge&env=production&limit=5"
257
281
  )
282
+ assert client.calls[10]["endpoint"] == "/api/authority/timelines/archive-repair"
283
+ assert client.calls[10]["method"] == "POST"
284
+ assert client.calls[10]["body"] == {
285
+ "since": "2026-04-01T00:00:00Z",
286
+ "until": "2026-04-02T00:00:00Z",
287
+ }
288
+ assert client.calls[11]["endpoint"] == "/api/authority/timelines/archive-verify"
289
+ assert client.calls[11]["body"] == {
290
+ "since": "2026-04-01T00:00:00Z",
291
+ "until": "2026-04-02T00:00:00Z",
292
+ }
293
+ assert client.calls[12]["endpoint"] == "/api/authority/timelines/archive-prune"
294
+ assert client.calls[12]["body"] == {
295
+ "since": "2026-01-01T00:00:00Z",
296
+ "until": "2026-01-02T00:00:00Z",
297
+ "retention_days": 365,
298
+ "dry_run": False,
299
+ "confirm": "DELETE_EXPIRED_ARCHIVE_OBJECTS",
300
+ }
258
301
 
259
302
 
260
303
  @pytest.mark.asyncio
@@ -81,13 +81,69 @@ async def test_logs_get_archive_status_forwards_filters():
81
81
  target_org_id="org-123",
82
82
  target_env="production",
83
83
  use_shared=True,
84
+ include_buffer=True,
84
85
  limit=5,
85
86
  )
86
87
 
87
88
  assert payload["events"][0]["event_id"] == "log-1"
88
89
  assert client.calls[0]["endpoint"] == (
89
90
  "/api/logs/archive-status?"
90
- "all_scopes=true&target_org_id=org-123&target_env=production&use_shared=true&limit=5"
91
+ "all_scopes=true&target_org_id=org-123&target_env=production&use_shared=true&include_buffer=true&limit=5"
91
92
  )
92
93
  assert client.calls[0]["method"] == "GET"
93
94
  assert client.calls[0]["use_gateway"] is True
95
+
96
+
97
+ @pytest.mark.asyncio
98
+ async def test_logs_archive_maintenance_helpers_forward_canonical_body():
99
+ client = FakeClient()
100
+ namespace = LogsNamespace(client)
101
+
102
+ await namespace.repair_archive_manifests(
103
+ since="2026-04-01T00:00:00Z",
104
+ until="2026-04-02T00:00:00Z",
105
+ target_org_id="org-123",
106
+ target_env="production",
107
+ use_shared=True,
108
+ )
109
+ await namespace.verify_archive_manifests(
110
+ since="2026-04-01T00:00:00Z",
111
+ until="2026-04-02T00:00:00Z",
112
+ target_org_id="org-123",
113
+ target_env="production",
114
+ use_shared=True,
115
+ )
116
+ await namespace.prune_archive_retention(
117
+ since="2026-01-01T00:00:00Z",
118
+ until="2026-01-02T00:00:00Z",
119
+ retention_days=90,
120
+ dry_run=False,
121
+ confirm="DELETE_EXPIRED_ARCHIVE_OBJECTS",
122
+ target_org_id="org-123",
123
+ target_env="production",
124
+ use_shared=True,
125
+ )
126
+
127
+ assert client.calls[0]["endpoint"] == "/api/logs/archive-repair"
128
+ assert client.calls[0]["method"] == "POST"
129
+ assert client.calls[0]["body"] == {
130
+ "since": "2026-04-01T00:00:00Z",
131
+ "until": "2026-04-02T00:00:00Z",
132
+ "target_org_id": "org-123",
133
+ "target_env": "production",
134
+ "use_shared": True,
135
+ }
136
+ assert client.calls[1]["endpoint"] == "/api/logs/archive-verify"
137
+ assert client.calls[1]["body"]["target_org_id"] == "org-123"
138
+ assert client.calls[1]["body"]["use_shared"] is True
139
+ assert client.calls[2]["endpoint"] == "/api/logs/archive-prune"
140
+ assert client.calls[2]["body"] == {
141
+ "since": "2026-01-01T00:00:00Z",
142
+ "until": "2026-01-02T00:00:00Z",
143
+ "retention_days": 90,
144
+ "dry_run": False,
145
+ "confirm": "DELETE_EXPIRED_ARCHIVE_OBJECTS",
146
+ "target_org_id": "org-123",
147
+ "target_env": "production",
148
+ "use_shared": True,
149
+ }
@@ -55,6 +55,32 @@ class RequestClient:
55
55
  return FakeResponse(json.dumps({"success": True, "data": {"ok": True}}))
56
56
 
57
57
 
58
+ class UnwrappedRequestClient:
59
+ calls = []
60
+
61
+ def __init__(self, *, base_url, headers, timeout):
62
+ self.base_url = base_url
63
+ self.headers = headers
64
+ self.timeout = timeout
65
+
66
+ async def __aenter__(self):
67
+ return self
68
+
69
+ async def __aexit__(self, exc_type, exc, tb):
70
+ return False
71
+
72
+ async def get(self, endpoint):
73
+ self.calls.append(
74
+ {
75
+ "endpoint": endpoint,
76
+ "base_url": self.base_url,
77
+ "headers": self.headers,
78
+ "timeout": self.timeout,
79
+ }
80
+ )
81
+ return FakeResponse(json.dumps({"secret": {"key": "PROVISION_REDIS_URL_REST", "value": "redis-url"}}))
82
+
83
+
58
84
  class ServiceJwtClient:
59
85
  def __init__(self, *, base_url, headers, timeout, proxy):
60
86
  self.base_url = base_url
@@ -132,6 +158,22 @@ async def test_request_accepts_raw_json_success_envelope(monkeypatch, sdk):
132
158
  assert RequestClient.calls[0]["endpoint"] == "/svc/health"
133
159
 
134
160
 
161
+ @pytest.mark.asyncio
162
+ async def test_request_accepts_unwrapped_control_plane_success(monkeypatch, sdk):
163
+ UnwrappedRequestClient.calls = []
164
+
165
+ async def fake_ensure_valid_jwt(psk_token, base_url):
166
+ return "jwt-abc"
167
+
168
+ monkeypatch.setattr(core_module, "_ensure_valid_jwt", fake_ensure_valid_jwt)
169
+ monkeypatch.setattr(start_module.httpx, "AsyncClient", UnwrappedRequestClient)
170
+
171
+ result = await sdk._request("/api/warden/secrets/PROVISION_REDIS_URL_REST", method="GET", use_gateway=True)
172
+
173
+ assert result == {"secret": {"key": "PROVISION_REDIS_URL_REST", "value": "redis-url"}}
174
+ assert UnwrappedRequestClient.calls[0]["endpoint"] == "/svc/warden/secrets/PROVISION_REDIS_URL_REST"
175
+
176
+
135
177
  def test_dominus_constructor_accepts_gateway_context_keywords():
136
178
  sdk = start_module.Dominus(
137
179
  gateway_user_token="user-jwt",