cortexdbai 0.13.0__tar.gz → 0.13.1__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 (147) hide show
  1. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/COVERAGE.md +3 -1
  2. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/PKG-INFO +49 -11
  3. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/README.md +48 -10
  4. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/__init__.py +1 -1
  5. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/v1/__init__.py +6 -0
  6. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/v1/_transport.py +15 -1
  7. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/v1/auth.py +33 -20
  8. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/v1/client.py +14 -7
  9. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/v1/errors.py +42 -11
  10. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/v1/exceptions.py +27 -0
  11. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/v1/models.py +51 -7
  12. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/v1/ops/access.py +150 -15
  13. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/v1/ops/admin.py +10 -2
  14. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/v1/ops/core.py +16 -9
  15. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/v1/ops/rest.py +14 -2
  16. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/pyproject.toml +1 -1
  17. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/scripts/error_codes.json +22 -8
  18. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/tests/live/test_live_shared_memory.py +13 -3
  19. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/tests/test_access_api.py +106 -0
  20. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/tests/test_errors_and_auth.py +23 -3
  21. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/LICENSE +0 -0
  22. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/__init__.py +0 -0
  23. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/ag2/__init__.py +0 -0
  24. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/ag2/agent.py +0 -0
  25. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/ag2/tools.py +0 -0
  26. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/agno/__init__.py +0 -0
  27. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/agno/memory.py +0 -0
  28. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/agno/tools.py +0 -0
  29. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/autogen/__init__.py +0 -0
  30. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/autogen/agent.py +0 -0
  31. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/autogen/tools.py +0 -0
  32. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/beeai/__init__.py +0 -0
  33. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/beeai/memory.py +0 -0
  34. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/beeai/tools.py +0 -0
  35. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/camel/__init__.py +0 -0
  36. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/camel/memory.py +0 -0
  37. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/camel/tools.py +0 -0
  38. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/controlflow/__init__.py +0 -0
  39. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/controlflow/memory.py +0 -0
  40. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/crewai/__init__.py +0 -0
  41. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/crewai/memory.py +0 -0
  42. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/crewai/tools.py +0 -0
  43. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/deepinfra/__init__.py +0 -0
  44. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/deepinfra/config.py +0 -0
  45. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/deepinfra/embeddings.py +0 -0
  46. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/deepinfra/openai_compat.py +0 -0
  47. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/dify/__init__.py +0 -0
  48. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/dify/manifest.yaml +0 -0
  49. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/dify/tool.py +0 -0
  50. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/dspy/__init__.py +0 -0
  51. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/dspy/memory.py +0 -0
  52. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/dspy/retriever.py +0 -0
  53. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/fireworks/__init__.py +0 -0
  54. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/fireworks/config.py +0 -0
  55. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/fireworks/embeddings.py +0 -0
  56. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/fireworks/openai_compat.py +0 -0
  57. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/flowise/__init__.py +0 -0
  58. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/flowise/node.py +0 -0
  59. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/google_adk/__init__.py +0 -0
  60. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/google_adk/tools.py +0 -0
  61. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/groq/__init__.py +0 -0
  62. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/groq/config.py +0 -0
  63. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/groq/embeddings.py +0 -0
  64. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/groq/openai_compat.py +0 -0
  65. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/hermes/__init__.py +0 -0
  66. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/hermes/provider.py +0 -0
  67. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/instructor/__init__.py +0 -0
  68. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/instructor/memory.py +0 -0
  69. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/langchain/__init__.py +0 -0
  70. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/langchain/memory.py +0 -0
  71. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/langchain/retriever.py +0 -0
  72. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/langchain/tools.py +0 -0
  73. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/langgraph/__init__.py +0 -0
  74. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/langgraph/memory.py +0 -0
  75. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/letta/__init__.py +0 -0
  76. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/letta/storage.py +0 -0
  77. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/llamaindex/__init__.py +0 -0
  78. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/llamaindex/memory.py +0 -0
  79. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/nemo_guardrails/__init__.py +0 -0
  80. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/nemo_guardrails/actions.py +0 -0
  81. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/nemo_guardrails/knowledge_base.py +0 -0
  82. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/ollama/__init__.py +0 -0
  83. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/ollama/config.py +0 -0
  84. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/ollama/embeddings.py +0 -0
  85. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/openai_agents/__init__.py +0 -0
  86. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/openai_agents/tools.py +0 -0
  87. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/prefect/__init__.py +0 -0
  88. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/prefect/blocks.py +0 -0
  89. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/prefect/tasks.py +0 -0
  90. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/pydanticai/__init__.py +0 -0
  91. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/pydanticai/dependencies.py +0 -0
  92. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/pydanticai/tools.py +0 -0
  93. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/smolagents/__init__.py +0 -0
  94. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/smolagents/tools.py +0 -0
  95. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/temporal/__init__.py +0 -0
  96. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/temporal/activities.py +0 -0
  97. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/temporal/interceptor.py +0 -0
  98. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/together/__init__.py +0 -0
  99. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/together/config.py +0 -0
  100. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/together/embeddings.py +0 -0
  101. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/together/openai_compat.py +0 -0
  102. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/vllm/__init__.py +0 -0
  103. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/vllm/config.py +0 -0
  104. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/vllm/embeddings.py +0 -0
  105. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/integrations/vllm/openai_compat.py +0 -0
  106. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/py.typed +0 -0
  107. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/v1/_wire.py +0 -0
  108. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/v1/consistency.py +0 -0
  109. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/v1/diagnostics.py +0 -0
  110. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/v1/fragments.py +0 -0
  111. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/v1/ops/__init__.py +0 -0
  112. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/v1/ops/code.py +0 -0
  113. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/v1/ops/erasure.py +0 -0
  114. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/v1/ops/native.py +0 -0
  115. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/v1/ops/org.py +0 -0
  116. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/v1/ops/scim.py +0 -0
  117. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/v1/records.py +0 -0
  118. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/cortexdb/v1/source.py +0 -0
  119. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/scripts/generate_errors.py +0 -0
  120. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/scripts/generate_models.py +0 -0
  121. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/tests/__init__.py +0 -0
  122. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/tests/_harness.py +0 -0
  123. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/tests/conftest.py +0 -0
  124. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/tests/live/__init__.py +0 -0
  125. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/tests/live/conftest.py +0 -0
  126. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/tests/test_admin_and_rest.py +0 -0
  127. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/tests/test_admin_retention.py +0 -0
  128. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/tests/test_answer_unavailable.py +0 -0
  129. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/tests/test_client_parity.py +0 -0
  130. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/tests/test_code_namespace.py +0 -0
  131. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/tests/test_conflicts.py +0 -0
  132. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/tests/test_consistency.py +0 -0
  133. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/tests/test_delete_blob.py +0 -0
  134. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/tests/test_erasures_forget.py +0 -0
  135. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/tests/test_integration_write_barriers.py +0 -0
  136. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/tests/test_matched_fragments.py +0 -0
  137. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/tests/test_org_routes.py +0 -0
  138. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/tests/test_read_write_fields.py +0 -0
  139. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/tests/test_readme_examples.py +0 -0
  140. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/tests/test_recall_hints_capabilities.py +0 -0
  141. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/tests/test_scim.py +0 -0
  142. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/tests/test_signup.py +0 -0
  143. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/tests/test_source.py +0 -0
  144. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/tests/test_streams.py +0 -0
  145. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/tests/test_unknown_method_guidance.py +0 -0
  146. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/tests/test_v093_surfaces.py +0 -0
  147. {cortexdbai-0.13.0 → cortexdbai-0.13.1}/tests/test_with_options.py +0 -0
@@ -320,8 +320,10 @@ Before the 0.14.0 work (`cortexdbai` 0.12.0), 67 operations were reached, 49 of
320
320
  | `idempotency_key` on a declared belief; `owner` on `POST /v1/scopes`; `scope` on a temporal phrase; `batch_id` on the lifecycle stream | those routes | `idempotency_key=`, `owner=`, `scope=`, `batch_id=` | covered |
321
321
  | `events_under_legal_hold`, `derived_under_legal_hold`, and `matched` counting what a hold kept | `POST /v1/forget`, `/v1/forget/preview` | in the answer (documented on both methods) | covered |
322
322
  | `in_band`, `in_band_parts` of a change set's preview; the operator key's in-band activation under `deny` (`BREAK_GLASS_REQUIRED` class `in_band`, `NOTICE_UNAVAILABLE`, `REGISTRY_STALE`) | `POST /v1/access/changes/{id}/preview`, `/activate` | `ChangeSetPreview` in `cortexdb.v1.models`; `client.break_glass(...).changeset_activate(...)` | covered |
323
+ | A change set's 202 while its evaluation runs (0.14.1; `Retry-After`, a preview's `{state: "previewing"}`) | `POST /v1/access/changes`, `/{id}/parts`, `/{id}/preview`, `/{id}/activate` | sent again after `Retry-After` for at most `wait` seconds (`CHANGESET_WAIT_SECONDS`), then `V1EvaluationPendingError`; `wait=0` returns `EvaluationPending` | covered |
324
+ | `operator_plane` of a tenant binding, its dry run's included (0.14.1) | `POST /v1/admin/access/tenant-binding` | `TenantBindingResponse` in `cortexdb.v1.models`; `admin_tenant_binding` | covered |
323
325
  | `deployment_mode` (`tenant_access_settings_v1`) | `GET`, `PUT /v1/admin/access/tenant-settings`; the access report's `tenant_settings` | `TenantAccessSettings` in `cortexdb.v1.models` | covered |
324
326
  | `resend` of a write status (`"no"` for a failed write, never re-sent; `"new_key"` gone) | `GET /v1/experience/status` | `write_status(...)`; `IndexingFailedError` says never to re-send | covered |
325
327
  | An omitted `X-Cortex-Actor` (issuers-file `prefix`, `hash` and `directory` tokens; SCIM clients) | every route | `actor=None`, `with_options(actor=None)`; derived from a token's `sub` when no `actor` is given (none for a mapped subject) | covered |
326
328
  | `view` left to the server (granular at an org tree's shared scope) | `/v1/recall`, `/v1/answer`, `/v1/compose` | `view=None` (the default) | covered |
327
- | Typed errors with `retriable` and `details` | every route | one class per code (`cortexdb.v1.errors`, 362 codes from `scripts/error_codes.json`, built from the error catalog, `docs/API_REFERENCE_V1.md` §16) under status-family classes; documented `details` keys as properties; `retry_after` | covered |
329
+ | Typed errors with `retriable` and `details` | every route | one class per code (`cortexdb.v1.errors`, 363 codes from `scripts/error_codes.json`, built from the error catalog, `docs/API_REFERENCE_V1.md` §16) under status-family classes; documented `details` keys as properties; `retry_after` | covered |
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: cortexdbai
3
- Version: 0.13.0
3
+ Version: 0.13.1
4
4
  Summary: The Long-Term Memory Layer for AI Systems
5
5
  Project-URL: Homepage, https://cortexdb.ai
6
6
  Project-URL: Documentation, https://docs.cortexdb.ai
@@ -319,6 +319,27 @@ sending it, with the code the server would answer. A value a route always
319
319
  refuses (an `extra_scopes` list past 40, a malformed break-glass header, a mint
320
320
  the server never accepts) raises `ValueError` before anything is sent.
321
321
 
322
+ ## What's new in 0.13.1 for CortexDB 0.14.1
323
+
324
+ - **Long change-set evaluations are waited for.** A staging, a preview or an
325
+ activation whose evaluation runs past the server's own wait answers 202
326
+ with `Retry-After`; each change-set method sends the same call again until
327
+ the answer comes, for at most `wait` seconds (120 by default,
328
+ `CHANGESET_WAIT_SECONDS`; `changeset_apply`'s bounds its three steps
329
+ together), then raises `V1EvaluationPendingError`. `wait=0` returns the
330
+ raw 202 as an `EvaluationPending`. The preview waited 60 s by default.
331
+ - **A version downgrade names what it drops**: `INVALID_ACCESS_RECORD` (a
332
+ change set without `convert`) or `INVALID_SCOPE_TREE` (a legacy registry
333
+ write), `reason == "version_downgrade"`, with `path` and `dropped`.
334
+ - **A tenant binding's dry run** answers `operator_plane`
335
+ `{registers_root, in_band, mode, break_glass_required}`.
336
+ - **Lifetime ceilings are the server's settings**: `lifetime_ceiling()` gives
337
+ the defaults, now 90 days for a person (a week for an agent, 30 days for a
338
+ service, a day for a chain token), `admin_metrics()["auth"]
339
+ ["lifetime_ceilings"]` the server's own, and `check_mint` leaves a chain
340
+ token's lifetime to the server. `InvalidBodyError` and
341
+ `InvalidTokenClaimError` read `lifetime_s` and `ceiling_s`.
342
+
322
343
  ## What's new in 0.13.0 for CortexDB 0.14.0
323
344
 
324
345
  `cortexdbai` 0.13.0 covers the whole v1 API of CortexDB 0.14.0
@@ -478,8 +499,20 @@ print(status["state"], status["consistency_token"][:4]) # activated ct1.
478
499
  A refusal raises its typed error: `PREVIEW_CHANGED` (the registry moved since
479
500
  the preview), `ACCESS_ASSERTION_FAILED` (a widening breaks one of the tenant's
480
501
  policy cases), `CHANGESET_STALE` (a record changed after staging),
481
- `ACCESS_LIMIT_REACHED`. `changeset_get` and `changeset_discard` read and drop a
482
- staged change set.
502
+ `ACCESS_LIMIT_REACHED`, and since 0.14.1 `INVALID_ACCESS_RECORD` with
503
+ `reason == "version_downgrade"` for a put that would make a version-2 record a
504
+ version-1 one (`err.dropped` lists the stored record's version-2 fields the put
505
+ leaves out: state them, or stage with `convert=True`). `changeset_get` and
506
+ `changeset_discard` read and drop a staged change set.
507
+
508
+ A large evaluation can run past the server's own wait (20 s by default): from
509
+ 0.14.1 a staging, a preview or an activation then answers 202 with
510
+ `Retry-After`, and each method sends the same call again until the answer
511
+ comes, for at most `wait` seconds in all (`CHANGESET_WAIT_SECONDS`, 120, by
512
+ default; `changeset_apply`'s `wait` bounds its three steps together). Past it
513
+ the method raises `V1EvaluationPendingError`, and the same call sent again
514
+ joins the evaluation. `wait=0` returns the first answer as it came, a 202 as
515
+ an `EvaluationPending` (its body, `status` and `retry_after`).
483
516
 
484
517
  Under a tenant whose operator mode is `deny`, the operator key's activation of
485
518
  a change set that registers a record, names a `service:` principal or widens
@@ -917,20 +950,25 @@ principal; its chain fields say who is acting for whom: `act` (an agent acting
917
950
  for the subject; it writes only with a `run`), `app` and `client` (an
918
951
  application, and whether a person or an agent drives it), `run` and
919
952
  `run_start`, and `taint` tags. `mint_agent_token` and `mint_app_token` mint
920
- the two common chains. A token with a chain field lives at most a day
921
- (`CHAIN_TOKEN_MAX_TTL_SECONDS`), and an agent chain token names its `caps`
922
- with `scopes` that confine each; the SDK refuses before sending what the
923
- server always refuses (`check_mint`, a `ValueError`). Other tokens live at
924
- most `lifetime_ceiling(subject)`: a week for a person or an agent, 30 days for
925
- a service. `with_token` derives a client that uses another token over the
926
- same connections.
953
+ the two common chains. A token with a chain field lives at most a day by
954
+ default (`CHAIN_TOKEN_MAX_TTL_SECONDS`), and an agent chain token names its
955
+ `caps` with `scopes` that confine each; the SDK refuses before sending what
956
+ the server always refuses (`check_mint`, a `ValueError`). Other tokens live at
957
+ most their kind's ceiling, by default `lifetime_ceiling(subject)`: 90 days for
958
+ a person (a week on a 0.14.0 server), a week for an agent, 30 days for a
959
+ service. Since 0.14.1 each ceiling is a server setting
960
+ (`CORTEX_MINT_MAX_LIFETIME_PERSON`, `_AGENT`, `_SERVICE`, `_CHAIN`):
961
+ `admin_metrics()["auth"]["lifetime_ceilings"]` reports the server's, and a
962
+ mint over one is 422 `INVALID_BODY` `ttl_too_long` with `ceiling_s`.
963
+ `with_token` derives a client that uses another token over the same
964
+ connections.
927
965
 
928
966
  <!-- example: tokens -->
929
967
  ```python
930
968
  from cortexdb.v1 import lifetime_ceiling
931
969
 
932
970
  erin = Cortex(URL, bearer=operator.mint_token(subject="user:erin", scope=ROOT)["token"])
933
- print(erin.actor, lifetime_ceiling("user:erin")) # user:erin 604800
971
+ print(erin.actor, lifetime_ceiling("user:erin")) # user:erin 7776000
934
972
 
935
973
  home = f"{ROOT}/user:alice"
936
974
  planner = alice.with_token(alice.mint_agent_token(
@@ -204,6 +204,27 @@ sending it, with the code the server would answer. A value a route always
204
204
  refuses (an `extra_scopes` list past 40, a malformed break-glass header, a mint
205
205
  the server never accepts) raises `ValueError` before anything is sent.
206
206
 
207
+ ## What's new in 0.13.1 for CortexDB 0.14.1
208
+
209
+ - **Long change-set evaluations are waited for.** A staging, a preview or an
210
+ activation whose evaluation runs past the server's own wait answers 202
211
+ with `Retry-After`; each change-set method sends the same call again until
212
+ the answer comes, for at most `wait` seconds (120 by default,
213
+ `CHANGESET_WAIT_SECONDS`; `changeset_apply`'s bounds its three steps
214
+ together), then raises `V1EvaluationPendingError`. `wait=0` returns the
215
+ raw 202 as an `EvaluationPending`. The preview waited 60 s by default.
216
+ - **A version downgrade names what it drops**: `INVALID_ACCESS_RECORD` (a
217
+ change set without `convert`) or `INVALID_SCOPE_TREE` (a legacy registry
218
+ write), `reason == "version_downgrade"`, with `path` and `dropped`.
219
+ - **A tenant binding's dry run** answers `operator_plane`
220
+ `{registers_root, in_band, mode, break_glass_required}`.
221
+ - **Lifetime ceilings are the server's settings**: `lifetime_ceiling()` gives
222
+ the defaults, now 90 days for a person (a week for an agent, 30 days for a
223
+ service, a day for a chain token), `admin_metrics()["auth"]
224
+ ["lifetime_ceilings"]` the server's own, and `check_mint` leaves a chain
225
+ token's lifetime to the server. `InvalidBodyError` and
226
+ `InvalidTokenClaimError` read `lifetime_s` and `ceiling_s`.
227
+
207
228
  ## What's new in 0.13.0 for CortexDB 0.14.0
208
229
 
209
230
  `cortexdbai` 0.13.0 covers the whole v1 API of CortexDB 0.14.0
@@ -363,8 +384,20 @@ print(status["state"], status["consistency_token"][:4]) # activated ct1.
363
384
  A refusal raises its typed error: `PREVIEW_CHANGED` (the registry moved since
364
385
  the preview), `ACCESS_ASSERTION_FAILED` (a widening breaks one of the tenant's
365
386
  policy cases), `CHANGESET_STALE` (a record changed after staging),
366
- `ACCESS_LIMIT_REACHED`. `changeset_get` and `changeset_discard` read and drop a
367
- staged change set.
387
+ `ACCESS_LIMIT_REACHED`, and since 0.14.1 `INVALID_ACCESS_RECORD` with
388
+ `reason == "version_downgrade"` for a put that would make a version-2 record a
389
+ version-1 one (`err.dropped` lists the stored record's version-2 fields the put
390
+ leaves out: state them, or stage with `convert=True`). `changeset_get` and
391
+ `changeset_discard` read and drop a staged change set.
392
+
393
+ A large evaluation can run past the server's own wait (20 s by default): from
394
+ 0.14.1 a staging, a preview or an activation then answers 202 with
395
+ `Retry-After`, and each method sends the same call again until the answer
396
+ comes, for at most `wait` seconds in all (`CHANGESET_WAIT_SECONDS`, 120, by
397
+ default; `changeset_apply`'s `wait` bounds its three steps together). Past it
398
+ the method raises `V1EvaluationPendingError`, and the same call sent again
399
+ joins the evaluation. `wait=0` returns the first answer as it came, a 202 as
400
+ an `EvaluationPending` (its body, `status` and `retry_after`).
368
401
 
369
402
  Under a tenant whose operator mode is `deny`, the operator key's activation of
370
403
  a change set that registers a record, names a `service:` principal or widens
@@ -802,20 +835,25 @@ principal; its chain fields say who is acting for whom: `act` (an agent acting
802
835
  for the subject; it writes only with a `run`), `app` and `client` (an
803
836
  application, and whether a person or an agent drives it), `run` and
804
837
  `run_start`, and `taint` tags. `mint_agent_token` and `mint_app_token` mint
805
- the two common chains. A token with a chain field lives at most a day
806
- (`CHAIN_TOKEN_MAX_TTL_SECONDS`), and an agent chain token names its `caps`
807
- with `scopes` that confine each; the SDK refuses before sending what the
808
- server always refuses (`check_mint`, a `ValueError`). Other tokens live at
809
- most `lifetime_ceiling(subject)`: a week for a person or an agent, 30 days for
810
- a service. `with_token` derives a client that uses another token over the
811
- same connections.
838
+ the two common chains. A token with a chain field lives at most a day by
839
+ default (`CHAIN_TOKEN_MAX_TTL_SECONDS`), and an agent chain token names its
840
+ `caps` with `scopes` that confine each; the SDK refuses before sending what
841
+ the server always refuses (`check_mint`, a `ValueError`). Other tokens live at
842
+ most their kind's ceiling, by default `lifetime_ceiling(subject)`: 90 days for
843
+ a person (a week on a 0.14.0 server), a week for an agent, 30 days for a
844
+ service. Since 0.14.1 each ceiling is a server setting
845
+ (`CORTEX_MINT_MAX_LIFETIME_PERSON`, `_AGENT`, `_SERVICE`, `_CHAIN`):
846
+ `admin_metrics()["auth"]["lifetime_ceilings"]` reports the server's, and a
847
+ mint over one is 422 `INVALID_BODY` `ttl_too_long` with `ceiling_s`.
848
+ `with_token` derives a client that uses another token over the same
849
+ connections.
812
850
 
813
851
  <!-- example: tokens -->
814
852
  ```python
815
853
  from cortexdb.v1 import lifetime_ceiling
816
854
 
817
855
  erin = Cortex(URL, bearer=operator.mint_token(subject="user:erin", scope=ROOT)["token"])
818
- print(erin.actor, lifetime_ceiling("user:erin")) # user:erin 604800
856
+ print(erin.actor, lifetime_ceiling("user:erin")) # user:erin 7776000
819
857
 
820
858
  home = f"{ROOT}/user:alice"
821
859
  planner = alice.with_token(alice.mint_agent_token(
@@ -84,7 +84,7 @@ from cortexdb import v1 # the explicit submodule namespace
84
84
  Cortex = V1Client
85
85
  AsyncCortex = AsyncV1Client
86
86
 
87
- __version__ = "0.13.0"
87
+ __version__ = "0.13.1"
88
88
  __all__ = [
89
89
  # Clients (preferred top-level names)
90
90
  "Cortex",
@@ -51,6 +51,7 @@ from cortexdb.v1.exceptions import (
51
51
  V1ConflictError,
52
52
  V1ConnectionError,
53
53
  V1Error,
54
+ V1EvaluationPendingError,
54
55
  V1GoneError,
55
56
  V1InvalidRequestError,
56
57
  V1MisdirectedRequestError,
@@ -94,6 +95,8 @@ from cortexdb.v1._transport import StreamEvent
94
95
  from cortexdb.v1.consistency import ConsistencyTokens
95
96
  from cortexdb.v1.ops.access import (
96
97
  ACCESS_ROLES,
98
+ CHANGESET_WAIT_SECONDS,
99
+ EvaluationPending,
97
100
  authzen_subject,
98
101
  changeset_part,
99
102
  delete_record,
@@ -133,6 +136,8 @@ __all__ = [
133
136
  "StreamEvent",
134
137
  "ConsistencyTokens",
135
138
  "ACCESS_ROLES",
139
+ "CHANGESET_WAIT_SECONDS",
140
+ "EvaluationPending",
136
141
  "authzen_subject",
137
142
  "changeset_part",
138
143
  "delete_record",
@@ -165,6 +170,7 @@ __all__ = [
165
170
  "V1ServerError",
166
171
  "V1ServerTimeoutError",
167
172
  "V1TimeoutError",
173
+ "V1EvaluationPendingError",
168
174
  "V1RateLimitError",
169
175
  "V1NotConfiguredError",
170
176
  "V1PolicyDeniedError",
@@ -77,12 +77,26 @@ class Poll:
77
77
 
78
78
  The wait between attempts is the response's ``Retry-After`` (seconds),
79
79
  else ``interval``; the last attempt starts before ``timeout`` seconds have
80
- passed, and its answer is returned whatever it is.
80
+ passed. ``pending`` narrows which answers of ``statuses`` are sent again
81
+ for (default: every one). An answer still pending once ``timeout`` has
82
+ passed is handed to ``expired`` with the seconds waited, when it is set;
83
+ otherwise it is returned as any other answer is.
81
84
  """
82
85
 
83
86
  statuses: frozenset
84
87
  timeout: float = 60.0
85
88
  interval: float = 1.0
89
+ pending: Optional[Callable[["Response"], bool]] = None
90
+ expired: Optional[Callable[["Response", float], Any]] = None
91
+
92
+ def again(self, response: "Response") -> bool:
93
+ """Whether ``response`` is one to send the request again for."""
94
+ return response.status in self.statuses and (self.pending is None or self.pending(response))
95
+
96
+ def pause(self, response: "Response") -> float:
97
+ """Seconds to wait before the next attempt: ``Retry-After``, else ``interval``."""
98
+ seconds = retry_after_seconds(response)
99
+ return self.interval if seconds is None else seconds
86
100
 
87
101
 
88
102
  #: How a consistency tracker may extend a request (see
@@ -1,4 +1,4 @@
1
- """Credentials and operator headers (CortexDB 0.14.0).
1
+ """Credentials and operator headers (CortexDB 0.14.0 and 0.14.1).
2
2
 
3
3
  * :func:`break_glass_header` builds ``X-Cortex-Break-Glass``
4
4
  (``operator_break_glass_v1``): ``reason=<reason>; ticket=<ticket>[;
@@ -11,7 +11,8 @@
11
11
  derives ``X-Cortex-Actor`` from a token's ``sub`` when no actor is given.
12
12
  * :func:`check_mint` holds a mint request to the rules the server always
13
13
  applies to the chain fields (``mint_chain_v1``), and
14
- :func:`lifetime_ceiling` gives the lifetime ceiling of a subject's kind.
14
+ :func:`lifetime_ceiling` gives the server's default lifetime ceiling of a
15
+ subject's kind (each a server setting since 0.14.1).
15
16
  """
16
17
 
17
18
  from __future__ import annotations
@@ -34,15 +35,22 @@ _TICKET = re.compile(r"^[A-Za-z0-9._:-]{1,64}$")
34
35
  _ACTOR = re.compile(r"^(user|agent|service|system):[A-Za-z0-9_.@-]{1,128}$")
35
36
 
36
37
  #: The longest a token minted with a chain field (``act``, ``app``,
37
- #: ``client``, ``run``, ``run_start``, ``taint``) lives, in seconds.
38
+ #: ``client``, ``run``, ``run_start``, ``taint``) lives by default, in
39
+ #: seconds: a day. Since 0.14.1 the server's ``CORTEX_MINT_MAX_LIFETIME_CHAIN``
40
+ #: sets it, whatever ``CORTEX_MINT_LIFETIME_ENFORCE`` says.
38
41
  CHAIN_TOKEN_MAX_TTL_SECONDS = 86_400
39
- #: The lifetime ceiling of a minted token by its subject's kind, in seconds:
40
- #: a week for a person or an agent, 30 days for a service or the system.
41
- #: The server enforces it unless its ``CORTEX_MINT_LIFETIME_ENFORCE`` is
42
- #: ``off`` (422 ``INVALID_BODY`` ``ttl_too_long`` at the mint; 401
43
- #: ``INVALID_TOKEN_CLAIM`` ``lifetime_over_ceiling`` for a token over it).
42
+ #: The default lifetime ceiling of a minted token by its subject's kind, in
43
+ #: seconds: 90 days for a person (``user:``; 7 days on a 0.14.0 server), a
44
+ #: week for an agent, 30 days for a service or the system. Since 0.14.1 each
45
+ #: is a server setting (``CORTEX_MINT_MAX_LIFETIME_PERSON``, ``_AGENT``,
46
+ #: ``_SERVICE``), and ``admin_metrics()["auth"]["lifetime_ceilings"]``
47
+ #: reports the ones a server runs with. The server enforces them unless its
48
+ #: ``CORTEX_MINT_LIFETIME_ENFORCE`` is ``off`` (422 ``INVALID_BODY``
49
+ #: ``ttl_too_long`` at the mint, with ``details.lifetime_s`` and
50
+ #: ``details.ceiling_s``; 401 ``INVALID_TOKEN_CLAIM`` ``lifetime_over_ceiling``
51
+ #: for a token over it).
44
52
  LIFETIME_CEILING_SECONDS = {
45
- "user": 604_800, "agent": 604_800, "service": 2_592_000, "system": 2_592_000,
53
+ "user": 7_776_000, "agent": 604_800, "service": 2_592_000, "system": 2_592_000,
46
54
  }
47
55
 
48
56
 
@@ -116,9 +124,13 @@ def is_actor_id(value: Any) -> bool:
116
124
 
117
125
 
118
126
  def lifetime_ceiling(subject: str) -> int:
119
- """The lifetime ceiling, in seconds, of a token minted for ``subject``
120
- (:data:`LIFETIME_CEILING_SECONDS`), before any chain field lowers it to
121
- :data:`CHAIN_TOKEN_MAX_TTL_SECONDS`."""
127
+ """The server's default lifetime ceiling, in seconds, of a token minted
128
+ for ``subject`` (:data:`LIFETIME_CEILING_SECONDS`), before any chain
129
+ field lowers it to the chain ceiling (:data:`CHAIN_TOKEN_MAX_TTL_SECONDS`
130
+ by default). A server may set each otherwise (0.14.1,
131
+ ``CORTEX_MINT_MAX_LIFETIME_*``): ``admin_metrics()["auth"]
132
+ ["lifetime_ceilings"]`` says what it runs with, and its refusal names
133
+ the ceiling (``details.ceiling_s``)."""
122
134
  kind = subject.split(":", 1)[0]
123
135
  if kind not in LIFETIME_CEILING_SECONDS:
124
136
  raise ValueError(f"not a principal id (user:, agent:, service:, system:): {subject!r}")
@@ -143,19 +155,20 @@ def check_mint(
143
155
  ``agent:`` id other than the subject; ``app`` a ``service:`` id;
144
156
  ``client`` ``"person"`` or ``"agent"``, and ``"person"`` never beside
145
157
  ``act`` or ``run``; ``run`` 1 to 128 bytes and ``run_start`` only with
146
- it; each ``taint`` tag 1 to 64 bytes; a token with any chain field lives
147
- at most a day; and an agent chain token (``act`` or ``run``, or ``app``
148
- without ``client="person"``, or ``client="agent"``) names its ``caps``
149
- with ``scopes`` that confine them. What the server checks against its
150
- state (an Owner of the application, ``auth.mint.on_behalf_of``, an AI
151
- employee named in ``act``) it answers itself."""
158
+ it; each ``taint`` tag 1 to 64 bytes; and an agent chain token (``act``
159
+ or ``run``, or ``app`` without ``client="person"``, or
160
+ ``client="agent"``) names its ``caps`` with ``scopes`` that confine them.
161
+ What the server checks against its settings or its state it answers
162
+ itself: the lifetime ceilings, the chain ceiling included (a day by
163
+ default, ``CORTEX_MINT_MAX_LIFETIME_CHAIN`` since 0.14.1: 422
164
+ ``INVALID_BODY`` ``ttl_too_long`` with ``details.lifetime_s`` and
165
+ ``details.ceiling_s``), an Owner of the application,
166
+ ``auth.mint.on_behalf_of``, an AI employee named in ``act``."""
152
167
  if not is_actor_id(subject):
153
168
  raise ValueError(f"not a principal id (user:, agent:, service:, system:): {subject!r}")
154
169
  chain = any(v is not None for v in (act, app, client, run, run_start, taint))
155
170
  if not chain:
156
171
  return
157
- if max(int(ttl_seconds), 60) > CHAIN_TOKEN_MAX_TTL_SECONDS:
158
- raise ValueError("a token that carries a chain field lives at most 86400 s (ttl_too_long)")
159
172
  if act is not None:
160
173
  if not (is_actor_id(act) and act.startswith("agent:")):
161
174
  raise ValueError("act is an agent: id (not_an_agent)")
@@ -41,7 +41,6 @@ from cortexdb.v1._transport import (
41
41
  async_stream_op,
42
42
  dig,
43
43
  page_spec,
44
- retry_after_seconds,
45
44
  sync_op,
46
45
  sync_proc,
47
46
  sync_stream_op,
@@ -549,14 +548,18 @@ class _SyncTransport(_ClientCore):
549
548
  return self._send_polling(req)
550
549
 
551
550
  def _send_polling(self, req: Request) -> Any:
552
- deadline = time.monotonic() + (req.poll.timeout if req.poll else 0.0)
551
+ poll = req.poll
552
+ start = time.monotonic()
553
+ deadline = start + (poll.timeout if poll else 0.0)
553
554
  while True:
554
555
  resp = self._send(req)
555
- if req.poll and resp.status in req.poll.statuses:
556
- wait = retry_after_seconds(resp) or req.poll.interval
556
+ if poll and poll.again(resp):
557
+ wait = poll.pause(resp)
557
558
  if time.monotonic() + wait < deadline:
558
559
  time.sleep(wait)
559
560
  continue
561
+ if poll.expired is not None:
562
+ return poll.expired(resp, time.monotonic() - start)
560
563
  return _finish(req, resp)
561
564
 
562
565
  def _send(self, req: Request, *, stream: bool = False) -> Any:
@@ -1031,14 +1034,18 @@ class _AsyncTransport(_ClientCore):
1031
1034
  return await self._send_polling(req)
1032
1035
 
1033
1036
  async def _send_polling(self, req: Request) -> Any:
1034
- deadline = time.monotonic() + (req.poll.timeout if req.poll else 0.0)
1037
+ poll = req.poll
1038
+ start = time.monotonic()
1039
+ deadline = start + (poll.timeout if poll else 0.0)
1035
1040
  while True:
1036
1041
  resp = await self._send(req)
1037
- if req.poll and resp.status in req.poll.statuses:
1038
- wait = retry_after_seconds(resp) or req.poll.interval
1042
+ if poll and poll.again(resp):
1043
+ wait = poll.pause(resp)
1039
1044
  if time.monotonic() + wait < deadline:
1040
1045
  await asyncio.sleep(wait)
1041
1046
  continue
1047
+ if poll.expired is not None:
1048
+ return poll.expired(resp, time.monotonic() - start)
1042
1049
  return _finish(req, resp)
1043
1050
 
1044
1051
  def _httpx_args(self, req: Request) -> Dict[str, Any]:
@@ -2010,17 +2010,21 @@ class InvalidAccessRecordError(V1ValidationError):
2010
2010
  change set's record, an application record (``PUT /v1/apps/{id}``), a registry write, or an
2011
2011
  edit of an application's reference root, whose shape is fixed (``reason: reference_root``);
2012
2012
  a ``when: {purpose}`` id outside a read's ``purpose`` grammar on a roster entry, a set entry
2013
- or a unit grant is ``reason: when_purpose``; ``details.path``, ``details.reason``.
2013
+ or a unit grant is ``reason: when_purpose``; a change set's put that would make a version-2
2014
+ record a version-1 one, without ``convert``, is ``reason: version_downgrade`` with
2015
+ ``details.dropped`` (since 0.14.1); ``details.path``, ``details.reason``.
2014
2016
 
2015
- Fix the record; ``details.path`` and ``details.reason`` say what.
2017
+ Fix the record; ``details.path`` and ``details.reason`` say what. For ``version_downgrade``,
2018
+ state the fields ``details.dropped`` names, or say ``convert``.
2016
2019
 
2017
- ``details``: ``path``, ``reason``.
2020
+ ``details``: ``path``, ``reason``, ``dropped``.
2018
2021
  """
2019
2022
 
2020
2023
  CODE = "INVALID_ACCESS_RECORD"
2021
2024
  STATUS = 422
2022
2025
  RETRIABLE = False
2023
2026
  path = _detail("path")
2027
+ dropped = _detail("dropped")
2024
2028
 
2025
2029
 
2026
2030
  class InvalidAuditError(V1ValidationError):
@@ -2043,7 +2047,7 @@ class InvalidBodyError(V1ValidationError):
2043
2047
  ``details.reason`` where known.
2044
2048
 
2045
2049
  ``details``: ``part``, ``reason``, ``field``, ``unsupported_fields``,
2046
- ``required_capabilities``, ``field_path``, ``entry``.
2050
+ ``required_capabilities``, ``field_path``, ``entry``, ``lifetime_s``, ``ceiling_s``.
2047
2051
  """
2048
2052
 
2049
2053
  CODE = "INVALID_BODY"
@@ -2055,6 +2059,8 @@ class InvalidBodyError(V1ValidationError):
2055
2059
  required_capabilities = _detail("required_capabilities")
2056
2060
  field_path = _detail("field_path")
2057
2061
  entry = _detail("entry")
2062
+ lifetime_s = _detail("lifetime_s")
2063
+ ceiling_s = _detail("ceiling_s")
2058
2064
 
2059
2065
 
2060
2066
  class InvalidBoundaryError(V1ValidationError):
@@ -2400,15 +2406,19 @@ class InvalidScopeGrammarError(V1ValidationError):
2400
2406
  class InvalidScopeTreeError(V1ValidationError):
2401
2407
  """422 ``INVALID_SCOPE_TREE`` (not retriable). Org-tree attributes out of their rules (0.13.0):
2402
2408
  ``details.reason`` names the rule, ``details.field`` the field (``tree.display_name`` is
2403
- refused under ``CORTEX_DISPLAY_NAME_IN_RECORD_ENFORCE``: put the name in the directory)
2409
+ refused under ``CORTEX_DISPLAY_NAME_IN_RECORD_ENFORCE``: put the name in the directory);
2410
+ since 0.14.1 also a legacy registry write that would make a version-2 record a version-1
2411
+ one, ``reason: version_downgrade``, with ``details.path`` and ``details.dropped``.
2404
2412
 
2405
- ``details``: ``reason``, ``field``.
2413
+ ``details``: ``reason``, ``field``, ``path``, ``dropped``.
2406
2414
  """
2407
2415
 
2408
2416
  CODE = "INVALID_SCOPE_TREE"
2409
2417
  STATUS = 422
2410
2418
  RETRIABLE = False
2411
2419
  field = _detail("field")
2420
+ path = _detail("path")
2421
+ dropped = _detail("dropped")
2412
2422
 
2413
2423
 
2414
2424
  class InvalidSubtreeError(V1BadRequestError):
@@ -2469,7 +2479,7 @@ class InvalidTokenClaimError(V1AuthError):
2469
2479
 
2470
2480
  Mint a correct token; an AI employee runs as itself, never through ``act``.
2471
2481
 
2472
- ``details``: ``claim``, ``reason``, ``issuer``.
2482
+ ``details``: ``claim``, ``reason``, ``issuer``, ``lifetime_s``, ``ceiling_s``.
2473
2483
  """
2474
2484
 
2475
2485
  CODE = "INVALID_TOKEN_CLAIM"
@@ -2477,6 +2487,8 @@ class InvalidTokenClaimError(V1AuthError):
2477
2487
  RETRIABLE = False
2478
2488
  claim = _detail("claim")
2479
2489
  issuer = _detail("issuer")
2490
+ lifetime_s = _detail("lifetime_s")
2491
+ ceiling_s = _detail("ceiling_s")
2480
2492
 
2481
2493
 
2482
2494
  class InvalidTokenSignatureError(V1AuthError):
@@ -2866,10 +2878,10 @@ class NoticeUnavailableError(V1UnavailableError):
2866
2878
 
2867
2879
  Retriable: yes, but for ``arming_disabled`` and ``reserved_path_in_use``.
2868
2880
 
2869
- Retry after the move; nothing was delivered. ``arming_disabled``: not until the operator
2870
- raises a ceiling set below the default; ``reserved_path_in_use``: not until the operator
2871
- migrates the scope's events out and deletes the record; ``members_behind``: once every
2872
- member is armed. ``limit``: retry once the age sweep has dropped older notices.
2881
+ Retry after the move; nothing was delivered. ``arming_disabled`` and
2882
+ ``reserved_path_in_use``: not until the operator raises the ceiling, or migrates the scope's
2883
+ events out and deletes the record; ``members_behind``: once every member is armed.
2884
+ ``limit``: retry once the age sweep has dropped older notices.
2873
2885
 
2874
2886
  ``details``: ``reason``, ``refusal``.
2875
2887
  """
@@ -3566,6 +3578,23 @@ class RepoScopeInUseError(V1ConflictError):
3566
3578
  RETRIABLE = False
3567
3579
 
3568
3580
 
3581
+ class RequestAbandonedError(V1UnavailableError):
3582
+ """503 ``REQUEST_ABANDONED`` (retriable). A change set's evaluation stopped: every caller
3583
+ waiting on it went away (since 0.14.1). The change-set routes (``POST /v1/access/changes``,
3584
+ ``…/parts``, ``…/preview``, ``…/activate``) and a SCIM Group write's checks answer it once
3585
+ the request's client has gone, and so has every other caller waiting on that evaluation.
3586
+ Nothing was staged or activated. Only a request whose client has gone meets it, so no client
3587
+ reads it.
3588
+
3589
+ None: only a request whose client has gone is answered with it, so no client reads it;
3590
+ nothing was staged or activated.
3591
+ """
3592
+
3593
+ CODE = "REQUEST_ABANDONED"
3594
+ STATUS = 503
3595
+ RETRIABLE = True
3596
+
3597
+
3569
3598
  class RequestTimeoutError(V1ServerTimeoutError):
3570
3599
  """408 ``REQUEST_TIMEOUT`` (not retriable). Cluster ingress: the body did not arrive within
3571
3600
  ``body_timeout_s``.
@@ -4937,6 +4966,7 @@ BY_CODE: Dict[str, Type[V1APIError]] = {
4937
4966
  "REPO_PURGE_PANICKED": RepoPurgePanickedError,
4938
4967
  "REPO_REGISTRY_WRITE_FAILED": RepoRegistryWriteFailedError,
4939
4968
  "REPO_SCOPE_IN_USE": RepoScopeInUseError,
4969
+ "REQUEST_ABANDONED": RequestAbandonedError,
4940
4970
  "REQUEST_TIMEOUT": RequestTimeoutError,
4941
4971
  "RESOLVE_FAILED": ResolveFailedError,
4942
4972
  "RESOURCE_DELETED": ResourceDeletedError,
@@ -5302,6 +5332,7 @@ __all__ = [
5302
5332
  "RepoPurgePanickedError",
5303
5333
  "RepoRegistryWriteFailedError",
5304
5334
  "RepoScopeInUseError",
5335
+ "RequestAbandonedError",
5305
5336
  "RequestTimeoutError",
5306
5337
  "ResolveFailedError",
5307
5338
  "ResourceDeletedError",
@@ -66,6 +66,33 @@ class V1TimeoutError(V1Error):
66
66
  """Request exceeded the configured client timeout."""
67
67
 
68
68
 
69
+ class V1EvaluationPendingError(V1TimeoutError):
70
+ """A change-set call's evaluation still ran when its ``wait`` ran out.
71
+
72
+ Since 0.14.1 a staging, a preview or an activation whose evaluation runs
73
+ past the server's wait answers 202 with ``Retry-After``, and the client
74
+ sends the same call again until it gets its answer, for at most the
75
+ call's ``wait`` seconds. Nothing failed and the work goes on: send the
76
+ same call again (it joins the evaluation, or is answered by it for 60 s
77
+ after it ended), or give it a longer ``wait``. ``method`` and ``path``
78
+ name the call, ``waited`` the seconds it waited, ``retry_after`` the
79
+ last answer's ``Retry-After`` and ``body`` its body (a preview's
80
+ ``{"state": "previewing"}``; a staging's and an activation's are empty).
81
+ """
82
+
83
+ def __init__(self, method: str, path: str, waited: float,
84
+ retry_after: Optional[float] = None, body: Any = None) -> None:
85
+ super().__init__(
86
+ f"{method} {path}: the change set's evaluation still runs after {waited:.0f} s "
87
+ "(202); send the same call again, or give it a longer wait"
88
+ )
89
+ self.method = method
90
+ self.path = path
91
+ self.waited = waited
92
+ self.retry_after = retry_after
93
+ self.body = body if body is not None else {}
94
+
95
+
69
96
  class V1APIError(V1Error):
70
97
  """Server returned a non-2xx response with a v1 error envelope.
71
98