matrx-orm 3.1.136__tar.gz → 3.1.138__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 (369) hide show
  1. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/.gitignore +3 -0
  2. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/PKG-INFO +2 -2
  3. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/FEATURE.md +7 -3
  4. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/__init__.py +20 -0
  5. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/catalog.py +55 -54
  6. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/FEATURE.md +6 -0
  7. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/expressions.py +57 -12
  8. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/resilience.py +10 -2
  9. matrx_orm-3.1.138/matrx_orm/core/session_context.py +222 -0
  10. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/operations/catalog_select.py +3 -1
  11. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/query/FEATURE.md +32 -0
  12. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/query/executor.py +40 -3
  13. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/session/README.md +21 -0
  14. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/session/coalesce.py +18 -1
  15. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/session/flush.py +62 -6
  16. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/session/op.py +24 -1
  17. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/session/session.py +49 -34
  18. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/pyproject.toml +2 -2
  19. matrx_orm-3.1.138/tests/level1/test_count_limit_param_leak.py +103 -0
  20. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_filter_jsonb_agg_array_index.py +52 -18
  21. matrx_orm-3.1.138/tests/level1/test_org_assignment_inventory.py +105 -0
  22. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_query_builder.py +2 -2
  23. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_resilience.py +32 -0
  24. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/session/test_flush_individual_fallback.py +33 -0
  25. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/test_dynamic_select_unregistered.py +1 -1
  26. matrx_orm-3.1.138/tests/test_order_by_desc_nulls_last.py +61 -0
  27. matrx_orm-3.1.136/matrx_orm/core/session_context.py +0 -94
  28. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/AGENTS.md +0 -0
  29. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/BASE_CLASS_METHODS.md +0 -0
  30. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/CLAUDE.md +0 -0
  31. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/MODEL_API.md +0 -0
  32. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/README.md +0 -0
  33. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/database/orm/extended/managers/ai_model_base.py +0 -0
  34. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/docs/migrations.md +0 -0
  35. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/MODULE_README.md +0 -0
  36. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/adapters/MODULE_README.md +0 -0
  37. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/adapters/__init__.py +0 -0
  38. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/adapters/async_postgresql.py +0 -0
  39. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/adapters/base_adapter.py +0 -0
  40. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/adapters/postgresql.py +0 -0
  41. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/adapters/postgrest_client_adapter.py +0 -0
  42. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/adapters/supabase_adapter.py +0 -0
  43. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/admin/FEATURE.md +0 -0
  44. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/admin/__init__.py +0 -0
  45. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/admin/router.py +0 -0
  46. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/api/__init__.py +0 -0
  47. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/api/auth.py +0 -0
  48. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/api/config.py +0 -0
  49. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/api/handlers.py +0 -0
  50. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/api/protocol.py +0 -0
  51. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/api/server.py +0 -0
  52. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/associations.py +0 -0
  53. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/cache_debug.py +0 -0
  54. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/catalog_sql.py +0 -0
  55. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/client/MODULE_README.md +0 -0
  56. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/client/__init__.py +0 -0
  57. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/client/postgres_connection.py +0 -0
  58. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/client/postgrest.py +0 -0
  59. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/client/supabase_auth.py +0 -0
  60. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/client/supabase_config.py +0 -0
  61. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/client/supabase_manager.py +0 -0
  62. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/EXTENDED-TASK.md +0 -0
  63. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/MODULE_README.md +0 -0
  64. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/RELATIONS-TASKS.md +0 -0
  65. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/TASKS.md +0 -0
  66. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/__init__.py +0 -0
  67. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/async_db_manager.py +0 -0
  68. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/base.py +0 -0
  69. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/config.py +0 -0
  70. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/connections.py +0 -0
  71. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/db_function.py +0 -0
  72. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/diagnostics.py +0 -0
  73. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/extended.py +0 -0
  74. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/fields.py +0 -0
  75. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/introspection.py +0 -0
  76. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/listen.py +0 -0
  77. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/loop_filters.py +0 -0
  78. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/model_dto.py +0 -0
  79. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/model_view.py +0 -0
  80. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/one_database.py +0 -0
  81. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/paginator.py +0 -0
  82. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/pool_maintenance.py +0 -0
  83. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/pool_watch.py +0 -0
  84. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/pooler_role.py +0 -0
  85. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/pydantic_bridge.py +0 -0
  86. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/registry.py +0 -0
  87. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/relations.py +0 -0
  88. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/rls_session.py +0 -0
  89. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/signals.py +0 -0
  90. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/tls.py +0 -0
  91. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/transaction.py +0 -0
  92. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/types.py +0 -0
  93. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/write_queue.py +0 -0
  94. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/entity.py +0 -0
  95. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/error_handling.py +0 -0
  96. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/exceptions.py +0 -0
  97. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/extended/__init__.py +0 -0
  98. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/extended/app_error_handler.py +0 -0
  99. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/identity_rule.py +0 -0
  100. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/local_sqlite.py +0 -0
  101. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/middleware/__init__.py +0 -0
  102. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/middleware/base.py +0 -0
  103. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/migrations/MODULE_README.md +0 -0
  104. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/migrations/__init__.py +0 -0
  105. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/migrations/cli.py +0 -0
  106. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/migrations/ddl.py +0 -0
  107. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/migrations/diff.py +0 -0
  108. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/migrations/executor.py +0 -0
  109. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/migrations/integration.py +0 -0
  110. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/migrations/loader.py +0 -0
  111. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/migrations/operations.py +0 -0
  112. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/migrations/state.py +0 -0
  113. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/migrations/table_filter.py +0 -0
  114. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/operations/FEATURE.md +0 -0
  115. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/operations/MODULE_README.md +0 -0
  116. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/operations/__init__.py +0 -0
  117. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/operations/bulk_update_values.py +0 -0
  118. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/operations/conflict.py +0 -0
  119. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/operations/conflict_writes.py +0 -0
  120. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/operations/create.py +0 -0
  121. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/operations/db_functions.py +0 -0
  122. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/operations/delete.py +0 -0
  123. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/operations/dynamic_admin.py +0 -0
  124. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/operations/dynamic_crud.py +0 -0
  125. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/operations/graph_walk.py +0 -0
  126. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/operations/latest_rows.py +0 -0
  127. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/operations/queue_claim.py +0 -0
  128. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/operations/read.py +0 -0
  129. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/operations/relational_backfill.py +0 -0
  130. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/operations/staging_load.py +0 -0
  131. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/operations/update.py +0 -0
  132. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/platform_access.py +0 -0
  133. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/platform_db.py +0 -0
  134. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/py.typed +0 -0
  135. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/pytest_plugin.py +0 -0
  136. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/python_sql/MODULE_README.md +0 -0
  137. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/python_sql/__init__.py +0 -0
  138. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/python_sql/db_objects.py +0 -0
  139. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/python_sql/table_detailed_relationships.py +0 -0
  140. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/python_sql/table_typescript_relationship.py +0 -0
  141. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/query/MODULE_README.md +0 -0
  142. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/query/__init__.py +0 -0
  143. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/query/builder.py +0 -0
  144. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/schema_builder/FEATURE.md +0 -0
  145. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/schema_builder/MODULE_README.md +0 -0
  146. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/schema_builder/__init__.py +0 -0
  147. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/schema_builder/code_handler.py +0 -0
  148. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/schema_builder/codegen_writer.py +0 -0
  149. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/schema_builder/columns.py +0 -0
  150. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/schema_builder/common.py +0 -0
  151. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/schema_builder/diff_preview.py +0 -0
  152. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/schema_builder/entity_capabilities.py +0 -0
  153. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/schema_builder/generator.py +0 -0
  154. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/schema_builder/helpers/__init__.py +0 -0
  155. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/schema_builder/helpers/base_generators.py +0 -0
  156. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/schema_builder/helpers/entity_generators.py +0 -0
  157. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/schema_builder/helpers/git_checker.py +0 -0
  158. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/schema_builder/package_wiring.py +0 -0
  159. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/schema_builder/relationships.py +0 -0
  160. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/schema_builder/runner.py +0 -0
  161. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/schema_builder/schema.py +0 -0
  162. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/schema_builder/schema_manager.py +0 -0
  163. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/schema_builder/tables.py +0 -0
  164. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/schema_builder/views.py +0 -0
  165. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/secrets_battery/FEATURE.md +0 -0
  166. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/secrets_battery/__init__.py +0 -0
  167. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/secrets_battery/attachments.py +0 -0
  168. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/secrets_battery/crypto.py +0 -0
  169. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/secrets_battery/host_authorization.py +0 -0
  170. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/secrets_battery/items.py +0 -0
  171. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/secrets_battery/model.py +0 -0
  172. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/secrets_battery/org_service.py +0 -0
  173. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/secrets_battery/service.py +0 -0
  174. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/session/__init__.py +0 -0
  175. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/session/dag.py +0 -0
  176. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/session/errors.py +0 -0
  177. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/session/fallback.py +0 -0
  178. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/session/lifecycle.py +0 -0
  179. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/session/managed.py +0 -0
  180. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/session/reads.py +0 -0
  181. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/session/telemetry.py +0 -0
  182. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/sql_executor/MODULE_README.md +0 -0
  183. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/sql_executor/__init__.py +0 -0
  184. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/sql_executor/executor.py +0 -0
  185. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/sql_executor/queries.py +0 -0
  186. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/sql_executor/registry.py +0 -0
  187. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/sql_executor/types.py +0 -0
  188. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/sql_executor/utils.py +0 -0
  189. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/state.py +0 -0
  190. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/utils/__init__.py +0 -0
  191. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/utils/sql_utils.py +0 -0
  192. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/utils/type_converters.py +0 -0
  193. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/scripts/git-branches.sh +0 -0
  194. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/scripts/publish.sh +0 -0
  195. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/scripts/release.sh +0 -0
  196. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/MODULE_README.md +0 -0
  197. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/__init__.py +0 -0
  198. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/conftest.py +0 -0
  199. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/MODULE_README.md +0 -0
  200. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/__init__.py +0 -0
  201. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_adapter_registry.py +0 -0
  202. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_admin_db_columns.py +0 -0
  203. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_admin_response_rendering.py +0 -0
  204. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_admin_search_negation.py +0 -0
  205. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_admin_search_sentinels.py +0 -0
  206. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_advisory_lock.py +0 -0
  207. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_agent_message.py +0 -0
  208. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_api_auth.py +0 -0
  209. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_api_config.py +0 -0
  210. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_api_handlers.py +0 -0
  211. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_api_protocol.py +0 -0
  212. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_array_agg_order_by.py +0 -0
  213. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_associations.py +0 -0
  214. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_associations_wiring.py +0 -0
  215. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_bulk_hydration_offload.py +0 -0
  216. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_bulk_update_by_pk.py +0 -0
  217. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_bulk_upsert_increment_set_fields.py +0 -0
  218. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_cache_debug.py +0 -0
  219. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_call_function.py +0 -0
  220. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_case_when.py +0 -0
  221. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_catalog.py +0 -0
  222. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_composite_pk_filter.py +0 -0
  223. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_composite_pk_write_paths.py +0 -0
  224. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_config.py +0 -0
  225. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_conflict_target.py +0 -0
  226. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_conflict_writes.py +0 -0
  227. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_connection_codecs.py +0 -0
  228. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_connection_poison_guard.py +0 -0
  229. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_connection_primitives.py +0 -0
  230. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_count_composite_distinct.py +0 -0
  231. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_db_function_field.py +0 -0
  232. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_ddl_generator.py +0 -0
  233. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_derived_table.py +0 -0
  234. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_dirty_tracking.py +0 -0
  235. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_dynamic_crud.py +0 -0
  236. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_exceptions.py +0 -0
  237. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_execute_query_integrity_mapping.py +0 -0
  238. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_expression_primitives.py +0 -0
  239. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_fields.py +0 -0
  240. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_from_alias.py +0 -0
  241. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_fts.py +0 -0
  242. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_func_expression_args.py +0 -0
  243. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_graph_walk.py +0 -0
  244. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_having_annotate_alias.py +0 -0
  245. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_insert_ignore_and_admin_primitives.py +0 -0
  246. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_introspect_rls_policies.py +0 -0
  247. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_join_and_aggregate.py +0 -0
  248. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_latest_rows.py +0 -0
  249. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_listen_notify.py +0 -0
  250. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_local_sqlite.py +0 -0
  251. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_managed_write_guard.py +0 -0
  252. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_matrx_entity.py +0 -0
  253. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_migration_diff_types.py +0 -0
  254. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_migration_loader.py +0 -0
  255. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_model_instance.py +0 -0
  256. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_model_meta.py +0 -0
  257. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_optimistic_concurrency.py +0 -0
  258. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_order_by_expression.py +0 -0
  259. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_parameter_name_hydration.py +0 -0
  260. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_patch_jsonb_path.py +0 -0
  261. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_persisted_count.py +0 -0
  262. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_pool_holder_census.py +0 -0
  263. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_pool_loop_recreation.py +0 -0
  264. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_pool_maintenance.py +0 -0
  265. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_postgrest_filters.py +0 -0
  266. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_pydantic_bridge.py +0 -0
  267. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_query_error_detail.py +0 -0
  268. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_query_executor_sql.py +0 -0
  269. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_query_timeout.py +0 -0
  270. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_querybuilder_clone_semantics.py +0 -0
  271. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_queue_claim.py +0 -0
  272. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_read_connection_retry.py +0 -0
  273. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_registry.py +0 -0
  274. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_registry_multi_database.py +0 -0
  275. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_registry_multi_schema.py +0 -0
  276. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_relational_backfill.py +0 -0
  277. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_relations.py +0 -0
  278. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_rls_session_scope.py +0 -0
  279. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_schema_exists.py +0 -0
  280. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_session_advisory_lock.py +0 -0
  281. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_state_cache.py +0 -0
  282. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_subquery_filter_raw.py +0 -0
  283. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_subquery_in_filter.py +0 -0
  284. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_subquery_nested_expressions.py +0 -0
  285. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_supabase_auth.py +0 -0
  286. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_supabase_config.py +0 -0
  287. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_tls.py +0 -0
  288. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_typed_join_projection.py +0 -0
  289. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_typed_predicates.py +0 -0
  290. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_update_case_expression.py +0 -0
  291. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_update_subquery.py +0 -0
  292. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_update_with_rebase.py +0 -0
  293. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_upsert_default_leak.py +0 -0
  294. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_upsert_with_conflict.py +0 -0
  295. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_values_jsonb_decode.py +0 -0
  296. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_watched_lifecycle.py +0 -0
  297. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_write_queue_deadline.py +0 -0
  298. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_write_retry_semantics.py +0 -0
  299. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level2/MODULE_README.md +0 -0
  300. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level2/__init__.py +0 -0
  301. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level2/conftest.py +0 -0
  302. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level2/test_bulk_ops.py +0 -0
  303. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level2/test_cache_integration.py +0 -0
  304. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level2/test_crud.py +0 -0
  305. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level2/test_foreign_keys.py +0 -0
  306. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level2/test_m2m.py +0 -0
  307. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level2/test_manager.py +0 -0
  308. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level2/test_migrations_live.py +0 -0
  309. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level2/test_query_execution.py +0 -0
  310. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level2/test_schema_diff.py +0 -0
  311. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/live_pooler_role.py +0 -0
  312. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/sample_project/.env.example +0 -0
  313. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/sample_project/README.md +0 -0
  314. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/sample_project/__init__.py +0 -0
  315. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/sample_project/generate.py +0 -0
  316. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/sample_project/generated/.gitkeep +0 -0
  317. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/sample_project/matrx_orm.yaml +0 -0
  318. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/sample_project/test_schema_generation.py +0 -0
  319. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/sample_project_desktop/.env.example +0 -0
  320. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/sample_project_desktop/README.md +0 -0
  321. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/sample_project_desktop/__init__.py +0 -0
  322. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/sample_project_desktop/client_example.py +0 -0
  323. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/sample_project_desktop/client_example.ts +0 -0
  324. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/sample_project_desktop/client_supabase_example.py +0 -0
  325. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/sample_project_desktop/server.py +0 -0
  326. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/schema/entity_tests.py +0 -0
  327. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/schema/test_base_generation.py +0 -0
  328. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/schema/test_composite_pk_fk_generation.py +0 -0
  329. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/schema/test_composite_unique_generation.py +0 -0
  330. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/schema/test_dto_identity_generation.py +0 -0
  331. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/schema/test_entity_capability_generation.py +0 -0
  332. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/schema/test_enum_ordering.py +0 -0
  333. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/schema/test_generate_schema.py +0 -0
  334. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/schema/test_junction_analysis.py +0 -0
  335. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/schema/test_multi_schema_output.py +0 -0
  336. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/schema/test_schema_analysis.py +0 -0
  337. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/schema/test_sql_expression_defaults.py +0 -0
  338. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/schema/test_table_relationships_query.py +0 -0
  339. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/schema/test_view_generation.py +0 -0
  340. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/schema_builder/test_array_default_parsing.py +0 -0
  341. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/schema_builder/test_focused_generation_plan.py +0 -0
  342. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/schema_builder/test_host_models_aggregator.py +0 -0
  343. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/session/__init__.py +0 -0
  344. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/session/conftest.py +0 -0
  345. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/session/test_cache_no_downgrade.py +0 -0
  346. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/session/test_capture_poison_proof.py +0 -0
  347. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/session/test_coalesce.py +0 -0
  348. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/session/test_dag.py +0 -0
  349. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/session/test_disk_spill_fallback.py +0 -0
  350. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/session/test_governed_write.py +0 -0
  351. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/session/test_managed.py +0 -0
  352. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/session/test_op_enqueue_site.py +0 -0
  353. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/session/test_reads.py +0 -0
  354. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/session/test_record_error_bounded_insert.py +0 -0
  355. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/session/test_record_error_storm.py +0 -0
  356. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/session/test_session.py +0 -0
  357. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/session/test_spill_guard_plugin.py +0 -0
  358. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/test_array_arg_element_type.py +0 -0
  359. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/test_database_name_alias.py +0 -0
  360. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/test_db_functions.py +0 -0
  361. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/test_keyed_digest.py +0 -0
  362. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/test_model_cls_refactor.py +0 -0
  363. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/test_no_blocking_asyncpg_tls.py +0 -0
  364. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/test_one_database.py +0 -0
  365. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/test_pooler_role_guard.py +0 -0
  366. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/test_secrets_battery.py +0 -0
  367. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/test_secrets_minting_keys.py +0 -0
  368. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/test_sql_param_casts.py +0 -0
  369. {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/test_sync_twins_guard.py +0 -0
@@ -301,3 +301,6 @@ apps/shared/*/*.tgz
301
301
  # capturing one mid-run.
302
302
  _kind_boundary_scratch*/
303
303
  _kind_marker_law_scratch*/
304
+
305
+ # packed npm tarballs are build output, never source (doctrine: typescript-package-standard)
306
+ *.tgz
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: matrx-orm
3
- Version: 3.1.136
3
+ Version: 3.1.138
4
4
  Summary: Async-first PostgreSQL ORM with bidirectional migrations, schema introspection, many-to-many relationships, and built-in state caching
5
5
  Project-URL: Homepage, https://github.com/AI-Matrix-Engine/aidream-current
6
6
  Project-URL: Repository, https://github.com/AI-Matrix-Engine/aidream-current
@@ -25,7 +25,7 @@ Requires-Python: >=3.13
25
25
  Requires-Dist: asyncpg>=0.31.0
26
26
  Requires-Dist: cryptography>=43.0
27
27
  Requires-Dist: gitpython>=3.1.40
28
- Requires-Dist: matrx-utils>=2.0.36
28
+ Requires-Dist: matrx-utils>=2.0.37
29
29
  Requires-Dist: psycopg-pool>=3.2.5
30
30
  Requires-Dist: psycopg[binary]>=3.2.5
31
31
  Requires-Dist: python-dotenv>=1.0
@@ -118,9 +118,13 @@
118
118
  the authoritative outcome channel.
119
119
  - 2026-08-21 — NO NULL ORG on the capture lane (db-rules §2). Both capture
120
120
  writers now stamp `organization_id` (`_capture_org()`); `ops.system_error` and
121
- `ops.system_write_failure` are `NOT NULL` with backstop trigger
122
- `_120_stamp_capture_org` (aidream migration 0443). `ops.system_error` left the
123
- `known_null_org_writers` allowlist in both ratchet baselines.
121
+ `ops.system_write_failure` are `NOT NULL`. Their historical assignment
122
+ attachment is migration debt, never a valid fallback for a missing org.
123
+ - 2026-09-12 — Replaced the old `org_backstop_coverage` health-shaped catalog
124
+ helper with `organization_assignment_inventory`. It reports the eight
125
+ schema-qualified known assigner functions and every `organization_id` column
126
+ default so the release ratchet can block growth without treating an automatic
127
+ assignment as protection.
124
128
  - 2026-08-21 — Fixed the `ON CONFLICT` retry counter in `_bulk_insert_failures`:
125
129
  it qualified the target as `public.system_write_failure`, which no longer
126
130
  exists, so every retry-conflict capture raised `UndefinedTableError` and
@@ -241,8 +241,18 @@ from .core.registry import (
241
241
  from .core.relations import ManyToManyField
242
242
  from .core.rls_session import rls_session
243
243
  from .core.session_context import (
244
+ ACTOR_SYSTEM_GUC,
245
+ ACTOR_TIER_GUC,
246
+ VALID_TIERS,
247
+ ActorRef,
248
+ ActorTier,
244
249
  SessionContextProvider,
245
250
  configure_session_context,
251
+ current_actor,
252
+ current_tier,
253
+ declared_actor,
254
+ declared_actor_gucs,
255
+ declaring_actor,
246
256
  session_context_gucs,
247
257
  )
248
258
  from .core.signals import (
@@ -638,7 +648,17 @@ __all__ = [
638
648
  "acquire_connection",
639
649
  "set_local_guc",
640
650
  "SessionContextProvider",
651
+ "ACTOR_SYSTEM_GUC",
652
+ "ACTOR_TIER_GUC",
653
+ "VALID_TIERS",
654
+ "ActorRef",
655
+ "ActorTier",
641
656
  "configure_session_context",
657
+ "current_actor",
658
+ "current_tier",
659
+ "declared_actor",
660
+ "declared_actor_gucs",
661
+ "declaring_actor",
642
662
  "session_context_gucs",
643
663
  "advisory_xact_lock",
644
664
  "try_advisory_xact_lock",
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import asyncio
5
6
  from typing import Any
6
7
 
7
8
  from .catalog_sql import PG_CATALOG_COLUMNS_CTE, PG_CATALOG_TABLES_CTE
@@ -19,7 +20,7 @@ __all__ = [
19
20
  "relation_column_counts",
20
21
  "relation_check_constraints",
21
22
  "relation_schemas",
22
- "org_backstop_coverage",
23
+ "organization_assignment_inventory",
23
24
  "publication_column_status",
24
25
  "relation_privileges",
25
26
  "role_service_state",
@@ -480,60 +481,60 @@ async def publication_column_status(
480
481
  return None if not rows else bool(rows[0]["published"])
481
482
 
482
483
 
483
- async def org_backstop_coverage(database: str) -> list[dict[str, Any]]:
484
- """Return required org-id relations with owner/backstop booleans."""
485
- return await _query(
486
- database,
487
- f"""
488
- WITH {PG_CATALOG_COLUMNS_CTE.strip()},
489
- {PG_CATALOG_TABLES_CTE.strip()}
490
- SELECT c.table_schema AS schema, c.table_name,
491
- EXISTS (SELECT 1 FROM pg_catalog_columns o
492
- WHERE o.table_schema=c.table_schema AND o.table_name=c.table_name
493
- AND o.column_name IN ('created_by','user_id','owner_id')) AS has_owner,
494
- EXISTS (SELECT 1 FROM pg_trigger t
495
- JOIN pg_class cl ON cl.oid=t.tgrelid
496
- JOIN pg_namespace n ON n.oid=cl.relnamespace
497
- JOIN pg_proc bp ON bp.oid=t.tgfoid
498
- WHERE n.nspname=c.table_schema AND cl.relname=c.table_name
499
- AND NOT t.tgisinternal
500
- -- Match the FUNCTION, not the trigger name: db-rules FEATURE.md
501
- -- §2 names THREE legal org backstops, and only the first is
502
- -- conventionally called `_stamp_org_default`. A child entity's
503
- -- backstop is `platform.inherit_org_from_parent` (trigger
504
- -- `_inherit_org`), and `plan` uses `_stamp_from_node`. Keying on
505
- -- the trigger name reported correctly-backstopped children as
506
- -- DRIFT (D232 §C, 2026-08-21).
507
- --
508
- -- FOUR, not three: `platform.stamp_run_org` (10 run tables) is a
509
- -- real backstop -- owner -> personal org, unattended -> SYSTEM
510
- -- org, else RAISE -- so it cannot leave organization_id NULL. It
511
- -- was missing here, and this gate is BLOCKING. Green only by
512
- -- luck: 8 of its 10 tables also carry `_stamp_org_default`, and
513
- -- the 2 that rely on it alone (docproc.page_extraction_runs,
514
- -- legal.ingest_runs) have no owner column, so has_owner is false
515
- -- and the gate skips them. Add a `created_by` to either and this
516
- -- gate would have blocked every release over a correctly
517
- -- backstopped table (matrx-frontend D241, 2026-08-21).
518
- --
519
- -- Adding a name here is a RULING, not housekeeping: read the
520
- -- function body first and confirm it cannot RETURN with a NULL
521
- -- organization_id. An accept-list that is too short blocks good
522
- -- releases; one that is too long silently passes a table whose
523
- -- org-forgetting write will 500 in production.
524
- AND bp.proname IN ('_stamp_org_default',
525
- 'inherit_org_from_parent',
526
- '_stamp_from_node',
527
- 'stamp_run_org')) AS has_backstop
528
- FROM pg_catalog_columns c
529
- JOIN pg_catalog_tables tb
530
- ON tb.table_schema=c.table_schema AND tb.table_name=c.table_name
531
- AND tb.table_type='BASE TABLE'
532
- WHERE c.column_name='organization_id' AND c.is_nullable='NO'
533
- AND c.column_default IS NULL
534
- ORDER BY c.table_schema, c.table_name
535
- """,
484
+ async def organization_assignment_inventory(
485
+ database: str,
486
+ *,
487
+ function_identities: list[tuple[str, str]],
488
+ ) -> dict[str, list[dict[str, Any]]]:
489
+ """Inventory known database organization assigners and column defaults.
490
+
491
+ This is an inventory of migration debt, never a health check. A writer
492
+ must provide ``organization_id`` itself; discovering one of these rows is
493
+ evidence to remove, not a reason to permit a missing value.
494
+ """
495
+ schemas = [schema for schema, _ in function_identities]
496
+ names = [name for _, name in function_identities]
497
+ trigger_rows, default_rows = await asyncio.gather(
498
+ _query(
499
+ database,
500
+ """
501
+ WITH requested AS (
502
+ SELECT function_schema, function_name
503
+ FROM unnest($1::text[], $2::text[])
504
+ AS requested(function_schema, function_name)
505
+ )
506
+ SELECT pns.nspname AS function_schema, p.proname AS function_name,
507
+ tns.nspname AS schema, c.relname AS table, t.tgname AS trigger
508
+ FROM pg_trigger t
509
+ JOIN pg_class c ON c.oid = t.tgrelid
510
+ JOIN pg_namespace tns ON tns.oid = c.relnamespace
511
+ JOIN pg_proc p ON p.oid = t.tgfoid
512
+ JOIN pg_namespace pns ON pns.oid = p.pronamespace
513
+ JOIN requested r ON r.function_schema = pns.nspname
514
+ AND r.function_name = p.proname
515
+ WHERE NOT t.tgisinternal
516
+ ORDER BY pns.nspname, p.proname, tns.nspname, c.relname, t.tgname
517
+ """,
518
+ schemas,
519
+ names,
520
+ ),
521
+ _query(
522
+ database,
523
+ """
524
+ SELECT n.nspname AS schema, c.relname AS table,
525
+ pg_get_expr(ad.adbin, ad.adrelid) AS default
526
+ FROM pg_attribute a
527
+ JOIN pg_class c ON c.oid = a.attrelid
528
+ JOIN pg_namespace n ON n.oid = c.relnamespace
529
+ JOIN pg_attrdef ad ON ad.adrelid = a.attrelid AND ad.adnum = a.attnum
530
+ WHERE a.attname = 'organization_id'
531
+ AND a.attnum > 0 AND NOT a.attisdropped
532
+ AND c.relkind = ANY ('{r,p}')
533
+ ORDER BY n.nspname, c.relname
534
+ """,
535
+ ),
536
536
  )
537
+ return {"triggers": trigger_rows, "defaults": default_rows}
537
538
 
538
539
 
539
540
  async def trigger_function_usage(
@@ -65,6 +65,12 @@ for scalar, row-set, discarded-result, and active-transaction calls. Callers can
65
65
  therefore distinguish an ambiguous in-flight RPC from a pool-acquire failure and
66
66
  must choose replay policy explicitly.
67
67
 
68
+ **Opt-in transient retries can be bounded and desynchronized.**
69
+ `retry_on_transient(..., jitter_ratio=...)` retains deterministic exponential
70
+ backoff by default; a caller recovering a replay-safe lock conflict can add at
71
+ most `jitter_ratio * base_delay` to each retry without changing its retry count
72
+ or exception classification.
73
+
68
74
  **Read-side connection loss retries once on a fresh checkout.** A read-only SQL
69
75
  statement is safe to repeat after `ConnectionDoesNotExistError`. The same retry
70
76
  applies when empty-read reproof loses its connection and transaction teardown
@@ -237,13 +237,35 @@ class Q:
237
237
 
238
238
  # -- Boolean operators --------------------------------------------------
239
239
 
240
+ @staticmethod
241
+ def _require_q(other: Any, op: str) -> None:
242
+ """Q composes with Q; Predicate composes with Predicate. Never mixed.
243
+
244
+ ``Q(a=1) | Is(F("x"), None)`` used to build a Q holding a Predicate
245
+ child and then die inside key validation with *\'Is\' object has no
246
+ attribute \'kwargs\'* — which is how GET /rag/admin/overview 500'd.
247
+ """
248
+ if isinstance(other, Q):
249
+ return
250
+ if isinstance(other, Predicate):
251
+ raise TypeError(
252
+ f"Q {op} {type(other).__name__}: a typed Predicate cannot be "
253
+ "combined with Q. Express the whole condition in ONE family — "
254
+ 'Compare(F("field"), "=", value) '
255
+ f"{op} {type(other).__name__}(...) — and pass that predicate "
256
+ "to filter()."
257
+ )
258
+ raise TypeError(f"Q {op} requires another Q, got {type(other).__name__}")
259
+
240
260
  def __and__(self, other: Q) -> Q:
261
+ self._require_q(other, "&")
241
262
  node = Q()
242
263
  node.connector = self.AND
243
264
  node.children = [self, other]
244
265
  return node
245
266
 
246
267
  def __or__(self, other: Q) -> Q:
268
+ self._require_q(other, "|")
247
269
  node = Q()
248
270
  node.connector = self.OR
249
271
  node.children = [self, other]
@@ -866,9 +888,7 @@ def _render_subquery_condition(
866
888
  expr_sql = value.as_sql(combined)
867
889
  sql_operator = _SUBQUERY_COMPARISON_OPERATORS.get(operator)
868
890
  if sql_operator is None:
869
- raise ValueError(
870
- f"Lookup {operator!r} is not supported with a database expression"
871
- )
891
+ raise ValueError(f"Lookup {operator!r} is not supported with a database expression")
872
892
  condition = f"{field_name} {sql_operator} {expr_sql}"
873
893
  elif isinstance(value, Exists):
874
894
  condition = value.as_sql(
@@ -879,9 +899,7 @@ def _render_subquery_condition(
879
899
  else:
880
900
  sub_sql = value.as_sql(current_table, combined)
881
901
  condition = (
882
- f"{field_name} IN {sub_sql}"
883
- if operator == "in"
884
- else f"{field_name} = {sub_sql}"
902
+ f"{field_name} IN {sub_sql}" if operator == "in" else f"{field_name} = {sub_sql}"
885
903
  )
886
904
  sub_params.extend(combined[already_bound:])
887
905
  return condition
@@ -1613,9 +1631,14 @@ def _describe_literal(value: Any) -> str:
1613
1631
  suffix = "," if len(value) == 1 else ""
1614
1632
  return "(" + ", ".join(_describe_literal(item) for item in value) + suffix + ")"
1615
1633
  if isinstance(value, dict):
1616
- return "{" + ", ".join(
1617
- f"{_describe_literal(key)}: {_describe_literal(item)}" for key, item in value.items()
1618
- ) + "}"
1634
+ return (
1635
+ "{"
1636
+ + ", ".join(
1637
+ f"{_describe_literal(key)}: {_describe_literal(item)}"
1638
+ for key, item in value.items()
1639
+ )
1640
+ + "}"
1641
+ )
1619
1642
  if isinstance(value, datetime | date):
1620
1643
  return value.isoformat()
1621
1644
  if isinstance(value, Decimal | UUID | str | int | float | bool) or value is None:
@@ -1653,9 +1676,13 @@ def _describe_expression(value: Any) -> str:
1653
1676
  def _describe_predicate(predicate: Predicate) -> str:
1654
1677
  """Render every built-in predicate form without SQL placeholders or object reprs."""
1655
1678
  if isinstance(predicate, CompoundPredicate):
1656
- return "(" + f" {predicate.connector} ".join(
1657
- _describe_predicate(child) for child in predicate.children
1658
- ) + ")"
1679
+ return (
1680
+ "("
1681
+ + f" {predicate.connector} ".join(
1682
+ _describe_predicate(child) for child in predicate.children
1683
+ )
1684
+ + ")"
1685
+ )
1659
1686
  if isinstance(predicate, NegatedPredicate):
1660
1687
  return f"NOT ({_describe_predicate(predicate.predicate)})"
1661
1688
  if isinstance(predicate, Compare):
@@ -1962,6 +1989,24 @@ class Filter:
1962
1989
  raise TypeError("Filter aggregate requires a typed SQL expression")
1963
1990
  if not isinstance(condition, Predicate):
1964
1991
  raise TypeError("Filter condition requires a typed Predicate")
1992
+ # Postgres attaches FILTER to an aggregate CALL and nothing else, so
1993
+ # ``Filter(ArrayIndex(ArrayAgg(x), 1), cond)`` emits
1994
+ # ``(array_agg(x))[1] FILTER (WHERE …)`` — *syntax error at or near
1995
+ # "FILTER"*. It shipped that way and 500'd GET /rag/admin/sources.
1996
+ # Refuse it here, where the remedy is obvious, instead of at the wire.
1997
+ # JsonbObjectAgg is also a PostgreSQL aggregate call, but it has two
1998
+ # positional expressions so it cannot inherit Aggregate's single-field
1999
+ # renderer. Keep the accepted set explicit rather than accepting any
2000
+ # Func wrapper (which would reintroduce invalid SQL at the wire).
2001
+ if not isinstance(agg, (Aggregate, JsonbObjectAgg)):
2002
+ raise TypeError(
2003
+ "Filter() attaches FILTER (WHERE ...) to an AGGREGATE CALL; "
2004
+ f"got {type(agg).__name__}, which Postgres rejects with "
2005
+ "'syntax error at or near \"FILTER\"'. Wrap the aggregate "
2006
+ "itself and apply the outer expression around the Filter — "
2007
+ "ArrayIndex(Filter(ArrayAgg(x), cond), 1), not "
2008
+ "Filter(ArrayIndex(ArrayAgg(x), 1), cond)."
2009
+ )
1965
2010
  self.agg = agg
1966
2011
  self.condition = condition
1967
2012
 
@@ -29,6 +29,7 @@ from __future__ import annotations
29
29
 
30
30
  import asyncio
31
31
  import logging
32
+ import random
32
33
  import re
33
34
  from typing import Awaitable, Callable, TypeVar
34
35
 
@@ -52,6 +53,7 @@ async def retry_on_transient(
52
53
  *,
53
54
  attempts: int = 3,
54
55
  base_backoff: float = 0.25,
56
+ jitter_ratio: float = 0.0,
55
57
  retry_on: tuple[type[BaseException], ...] = DEFAULT_TRANSIENT,
56
58
  label: str = "db-unit",
57
59
  ) -> T:
@@ -59,13 +61,17 @@ async def retry_on_transient(
59
61
 
60
62
  ``factory`` is a zero-arg async callable invoked fresh on every attempt, so
61
63
  it must (re)open its own transaction/connection — a rolled-back attempt
62
- leaves nothing behind. Backoff is exponential from ``base_backoff``. On the
63
- final attempt the original exception propagates unchanged.
64
+ leaves nothing behind. Backoff is exponential from ``base_backoff``. An
65
+ optional positive ``jitter_ratio`` adds up to that fraction of each base
66
+ delay, so callers recovering lock contention need not retry in lockstep.
67
+ On the final attempt the original exception propagates unchanged.
64
68
 
65
69
  Loud on every retry AND on heal, so a masked slow-query/contention problem
66
70
  is never silent — it shows up as a WARNING you can grep, not a swallowed
67
71
  hiccup.
68
72
  """
73
+ if jitter_ratio < 0:
74
+ raise ValueError("jitter_ratio must be non-negative")
69
75
  last_exc: BaseException | None = None
70
76
  for attempt in range(1, attempts + 1):
71
77
  try:
@@ -91,6 +97,8 @@ async def retry_on_transient(
91
97
  )
92
98
  raise
93
99
  backoff = base_backoff * (2 ** (attempt - 1))
100
+ if jitter_ratio:
101
+ backoff += random.uniform(0.0, backoff * jitter_ratio) # noqa: S311 -- retry desynchronization
94
102
  logger.warning(
95
103
  "[resilience] %s attempt %d/%d hit transient DB failure (%s) — "
96
104
  "retrying in %.2fs (rolled back, safe to re-run).",
@@ -0,0 +1,222 @@
1
+ """The host's DECLARED session context — GUCs every transaction opens with.
2
+
3
+ Some facts about a block of work are known by the application and readable only
4
+ by the database: *which tier of actor* is authoring these writes, for instance.
5
+ Postgres learns them through transaction-local GUCs (``set_config(..., true)``),
6
+ and a GUC set outside a transaction has no meaning — so the ONE place that can
7
+ reliably apply them is the moment this package opens a transaction.
8
+
9
+ The host declares a provider once at startup::
10
+
11
+ from matrx_orm import configure_session_context
12
+ configure_session_context(lambda: {"app.actor_tier": "ai", "app.actor_system": "x"})
13
+
14
+ and from then on every TOP-LEVEL ``transaction()`` (which includes the one
15
+ ``rls_session`` opens, before it drops to the restricted role) emits whatever
16
+ the provider returns at that instant. A savepoint (nested transaction) emits
17
+ nothing: it is already inside the transaction the GUCs were set on.
18
+
19
+ Why a provider and not a value: what is true changes per task — the provider is
20
+ normally a ContextVar read, so a block that declares "an AI is writing this"
21
+ affects exactly the work inside it and nothing running beside it.
22
+
23
+ The package NEVER imports the host: no provider configured ⇒ no GUCs, which is
24
+ exactly how matrx-orm behaves standalone. A provider that raises is a host bug
25
+ and it surfaces — the transaction fails loudly rather than silently committing
26
+ writes whose provenance is a lie.
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ import logging
32
+ import re
33
+ from collections.abc import AsyncIterator, Callable, Iterator, Mapping
34
+ from contextlib import asynccontextmanager, contextmanager
35
+ from contextvars import ContextVar
36
+ from dataclasses import dataclass
37
+ from typing import Any, Literal
38
+
39
+ logger = logging.getLogger("matrx_orm.core.session_context")
40
+
41
+ # Same shape matrx_orm.core.transaction.set_local_guc accepts.
42
+ _GUC_NAME_RE = re.compile(r"^[a-z_][a-z0-9_.]*$")
43
+
44
+ SessionContextProvider = Callable[[], Mapping[str, str] | None]
45
+
46
+ _provider: SessionContextProvider | None = None
47
+
48
+
49
+ def configure_session_context(provider: SessionContextProvider | None) -> None:
50
+ """Bind (or with ``None``, unbind) the host's session-context provider.
51
+
52
+ Call once at startup, alongside the other package ``configure_*`` calls.
53
+ ``provider`` is invoked on every top-level transaction open and must return
54
+ a mapping of GUC name → value (or ``None`` / ``{}`` for "nothing to say").
55
+ """
56
+ global _provider
57
+ if provider is not None and not callable(provider):
58
+ raise TypeError("configure_session_context(provider) requires a callable or None")
59
+ _provider = provider
60
+
61
+
62
+ def session_context_gucs() -> dict[str, str]:
63
+ """The GUCs to set on a transaction being opened right now.
64
+
65
+ Empty when no provider is configured. Raises when a configured provider
66
+ returns something unusable — a malformed declaration is a host defect, and
67
+ writing rows with a wrong (or absent) provenance stamp is worse than a loud
68
+ failure at BEGIN.
69
+ """
70
+ provider = _provider
71
+ if provider is None:
72
+ return {}
73
+ declared = provider()
74
+ if not declared:
75
+ return {}
76
+ if not isinstance(declared, Mapping):
77
+ raise TypeError(
78
+ "the session-context provider must return a mapping of GUC name → value; "
79
+ f"got {type(declared).__name__}"
80
+ )
81
+ out: dict[str, str] = {}
82
+ for name, value in declared.items():
83
+ if not isinstance(name, str) or not _GUC_NAME_RE.match(name):
84
+ raise ValueError(f"invalid session-context GUC name: {name!r}")
85
+ out[name] = "" if value is None else str(value)
86
+ return out
87
+
88
+
89
+ async def apply_session_context(connection: Any) -> None:
90
+ """Emit the declared GUCs on ``connection`` (already inside a BEGIN)."""
91
+ for name, value in session_context_gucs().items():
92
+ await connection.execute("SELECT set_config($1, $2, true)", name, value)
93
+
94
+
95
+ # ---------------------------------------------------------------------------
96
+ # THE ACTOR DECLARATION — who authored the writes in a block.
97
+ #
98
+ # This lives HERE, beside the provider that emits it, because the declaration
99
+ # is only useful to a package that can never import the host: the chat door in
100
+ # matrx-ai declares "a person typed this row" / "the model produced this row",
101
+ # and matrx-orm is the common floor both the host and that package stand on.
102
+ # The host (aidream) re-exports these names and wires ``declared_actor_gucs``
103
+ # into ``configure_session_context`` at startup.
104
+ #
105
+ # CONVERGE: actor tier literal code|ai|human; target vocabulary
106
+ # system|agent|user - declared 2026-09-10, Data Doctrine 1.5/7.
107
+ # ---------------------------------------------------------------------------
108
+
109
+ ActorTier = Literal["code", "ai", "human"]
110
+
111
+ ACTOR_TIER_GUC = "app.actor_tier"
112
+ ACTOR_SYSTEM_GUC = "app.actor_system"
113
+
114
+ VALID_TIERS: frozenset[str] = frozenset({"code", "ai", "human"})
115
+
116
+
117
+ @dataclass(frozen=True, slots=True)
118
+ class ActorRef:
119
+ """The actor a block of DB work is attributed to."""
120
+
121
+ tier: ActorTier
122
+ system: str | None = None
123
+
124
+ def as_gucs(self) -> dict[str, str]:
125
+ """The two session GUCs this actor implies."""
126
+ return {ACTOR_TIER_GUC: self.tier, ACTOR_SYSTEM_GUC: self.system or ""}
127
+
128
+
129
+ _current_actor: ContextVar[ActorRef | None] = ContextVar("_provenance_actor", default=None)
130
+
131
+
132
+ def current_actor() -> ActorRef | None:
133
+ """The actor declared by the innermost open declaration, if any."""
134
+ return _current_actor.get()
135
+
136
+
137
+ def current_tier() -> ActorTier | None:
138
+ """The declared tier, or None when nothing declared one."""
139
+ actor = _current_actor.get()
140
+ return actor.tier if actor else None
141
+
142
+
143
+ def declared_actor_gucs() -> dict[str, str]:
144
+ """The session GUCs the currently declared actor implies.
145
+
146
+ This is the provider a host passes to :func:`configure_session_context`.
147
+ Empty when nothing declared an actor: the DB resolver then applies its
148
+ documented default (``human`` with an identity present, ``code`` without)
149
+ and logs that it did.
150
+ """
151
+ actor = _current_actor.get()
152
+ if actor is None:
153
+ return {}
154
+ return actor.as_gucs()
155
+
156
+
157
+ def _validated_actor(tier: str, system: str, *, caller: str) -> ActorRef:
158
+ if tier not in VALID_TIERS:
159
+ raise ValueError(f"{caller} tier must be one of {sorted(VALID_TIERS)}; got {tier!r}")
160
+ if not system or not system.strip():
161
+ raise ValueError(f"{caller} requires a non-empty system (who specifically)")
162
+ return ActorRef(tier=tier, system=system.strip()) # type: ignore[arg-type]
163
+
164
+
165
+ @contextmanager
166
+ def declaring_actor(tier: ActorTier, system: str) -> Iterator[ActorRef]:
167
+ """Synchronous form of :func:`declared_actor` - same ContextVar, no await.
168
+
169
+ A write door is often a plain function (``queue_message_create``), and the
170
+ thing that must carry the declaration is the SYNCHRONOUS act of queueing
171
+ the op: a deferred write commits later, inside a flush that ran long after
172
+ this block closed, so the op itself captures the actor declared here
173
+ (``matrx_orm.session.op``) and the flush re-declares it per row. Nothing
174
+ here touches a connection.
175
+ """
176
+ actor = _validated_actor(tier, system, caller="declaring_actor")
177
+ token = _current_actor.set(actor)
178
+ try:
179
+ yield actor
180
+ finally:
181
+ _current_actor.reset(token)
182
+
183
+
184
+ @asynccontextmanager
185
+ async def declared_actor(tier: ActorTier, system: str) -> AsyncIterator[ActorRef]:
186
+ """Declare the actor for the block WITHOUT opening a transaction.
187
+
188
+ The declaration is a ContextVar, so the GUCs land when the work inside
189
+ opens its own transaction (this module's provider is asked at every
190
+ top-level BEGIN, ``rls_session`` included) - or, for a write deferred onto
191
+ a Session, when the op that captured this declaration is executed at flush.
192
+
193
+ That is what makes it usable at a DISPATCHER, above code whose write paths
194
+ are its own business: ``rls_session`` refuses to nest inside an existing
195
+ transaction, so a chokepoint that opened one would break every handler that
196
+ acts as the user.
197
+ """
198
+ actor = _validated_actor(tier, system, caller="declared_actor")
199
+ token = _current_actor.set(actor)
200
+ logger.debug("provenance: declared tier=%s system=%s", actor.tier, actor.system)
201
+ try:
202
+ yield actor
203
+ finally:
204
+ _current_actor.reset(token)
205
+
206
+
207
+ __all__ = [
208
+ "ACTOR_SYSTEM_GUC",
209
+ "ACTOR_TIER_GUC",
210
+ "VALID_TIERS",
211
+ "ActorRef",
212
+ "ActorTier",
213
+ "SessionContextProvider",
214
+ "apply_session_context",
215
+ "configure_session_context",
216
+ "current_actor",
217
+ "current_tier",
218
+ "declared_actor",
219
+ "declared_actor_gucs",
220
+ "declaring_actor",
221
+ "session_context_gucs",
222
+ ]
@@ -238,7 +238,9 @@ async def catalog_select(
238
238
  parts = []
239
239
  for field in order_by:
240
240
  descending = field.startswith("-")
241
- parts.append(f"{_quote(field.removeprefix('-'))} {'DESC' if descending else 'ASC'}")
241
+ # "-col" = largest/newest first; a NULL is never the newest, so DESC
242
+ # gets NULLS LAST (Postgres' default for DESC is NULLS FIRST).
243
+ parts.append(f"{_quote(field.removeprefix('-'))} {'DESC NULLS LAST' if descending else 'ASC'}")
242
244
  sql += " ORDER BY " + ", ".join(parts)
243
245
 
244
246
  if limit is not None: