matrx-orm 3.1.199__tar.gz → 3.1.201__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 (431) hide show
  1. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/CHANGELOG.md +5 -0
  2. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/PKG-INFO +1 -1
  3. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/__init__.py +2 -0
  4. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/core/FEATURE.md +52 -0
  5. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/core/async_db_manager.py +34 -0
  6. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/core/pooler_role.py +64 -0
  7. matrx_orm-3.1.201/matrx_orm/core/read_write.py +221 -0
  8. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/core/rls_session.py +22 -31
  9. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/core/session_context.py +35 -2
  10. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/core/transaction.py +53 -10
  11. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/pytest_lock_guard.py +12 -4
  12. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/session/flush.py +2 -3
  13. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/write_actor.py +2 -3
  14. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/pyproject.toml +1 -1
  15. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_association_trigger_actor_naming.py +3 -3
  16. matrx_orm-3.1.201/tests/level1/test_read_write_declared.py +108 -0
  17. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_rls_session_scope.py +39 -21
  18. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_transaction_database_identity.py +7 -3
  19. matrx_orm-3.1.201/tests/level1/test_transaction_gucs_one_round_trip.py +91 -0
  20. matrx_orm-3.1.201/tests/level1/test_transaction_pool_release_sends_nothing.py +93 -0
  21. matrx_orm-3.1.201/tests/test_a_backend_handed_over_mid_transaction_never_takes_our_writes.py +311 -0
  22. matrx_orm-3.1.201/tests/test_a_read_only_backend_never_refuses_our_writes.py +187 -0
  23. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/test_pooler_role_guard.py +111 -4
  24. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/.gitignore +0 -0
  25. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/AGENTS.md +0 -0
  26. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/BASE_CLASS_METHODS.md +0 -0
  27. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/CLAUDE.md +0 -0
  28. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/MODEL_API.md +0 -0
  29. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/README.md +0 -0
  30. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/database/orm/extended/managers/ai_model_base.py +0 -0
  31. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/docs/migrations.md +0 -0
  32. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/FEATURE.md +0 -0
  33. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/MODULE_README.md +0 -0
  34. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/adapters/MODULE_README.md +0 -0
  35. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/adapters/__init__.py +0 -0
  36. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/adapters/async_postgresql.py +0 -0
  37. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/adapters/base_adapter.py +0 -0
  38. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/adapters/postgrest_client_adapter.py +0 -0
  39. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/adapters/supabase_adapter.py +0 -0
  40. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/admin/FEATURE.md +0 -0
  41. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/admin/__init__.py +0 -0
  42. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/admin/router.py +0 -0
  43. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/api/__init__.py +0 -0
  44. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/api/auth.py +0 -0
  45. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/api/config.py +0 -0
  46. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/api/handlers.py +0 -0
  47. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/api/protocol.py +0 -0
  48. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/api/server.py +0 -0
  49. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/associations.py +0 -0
  50. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/cache_debug.py +0 -0
  51. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/catalog.py +0 -0
  52. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/catalog_sql.py +0 -0
  53. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/client/MODULE_README.md +0 -0
  54. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/client/__init__.py +0 -0
  55. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/client/postgres_connection.py +0 -0
  56. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/client/postgrest.py +0 -0
  57. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/client/supabase_auth.py +0 -0
  58. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/client/supabase_config.py +0 -0
  59. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/client/supabase_manager.py +0 -0
  60. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/core/EXTENDED-TASK.md +0 -0
  61. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/core/MODULE_README.md +0 -0
  62. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/core/RELATIONS-TASKS.md +0 -0
  63. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/core/TASKS.md +0 -0
  64. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/core/__init__.py +0 -0
  65. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/core/admin_lane.py +0 -0
  66. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/core/base.py +0 -0
  67. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/core/config.py +0 -0
  68. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/core/connections.py +0 -0
  69. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/core/db_function.py +0 -0
  70. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/core/diagnostics.py +0 -0
  71. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/core/expressions.py +0 -0
  72. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/core/extended.py +0 -0
  73. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/core/fields.py +0 -0
  74. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/core/introspection.py +0 -0
  75. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/core/listen.py +0 -0
  76. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/core/loop_filters.py +0 -0
  77. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/core/model_dto.py +0 -0
  78. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/core/model_view.py +0 -0
  79. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/core/one_database.py +0 -0
  80. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/core/paginator.py +0 -0
  81. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/core/pool_maintenance.py +0 -0
  82. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/core/pool_watch.py +0 -0
  83. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/core/pydantic_bridge.py +0 -0
  84. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/core/registry.py +0 -0
  85. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/core/relations.py +0 -0
  86. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/core/resilience.py +0 -0
  87. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/core/signals.py +0 -0
  88. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/core/statement_ceiling.py +0 -0
  89. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/core/tls.py +0 -0
  90. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/core/types.py +0 -0
  91. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/core/write_queue.py +0 -0
  92. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/core/write_retry.py +0 -0
  93. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/entity.py +0 -0
  94. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/error_handling.py +0 -0
  95. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/exceptions.py +0 -0
  96. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/extended/__init__.py +0 -0
  97. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/extended/app_error_handler.py +0 -0
  98. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/identity_rule.py +0 -0
  99. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/local_sqlite.py +0 -0
  100. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/middleware/__init__.py +0 -0
  101. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/middleware/base.py +0 -0
  102. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/migrations/MODULE_README.md +0 -0
  103. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/migrations/__init__.py +0 -0
  104. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/migrations/cli.py +0 -0
  105. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/migrations/ddl.py +0 -0
  106. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/migrations/diff.py +0 -0
  107. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/migrations/executor.py +0 -0
  108. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/migrations/integration.py +0 -0
  109. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/migrations/loader.py +0 -0
  110. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/migrations/operations.py +0 -0
  111. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/migrations/state.py +0 -0
  112. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/migrations/table_filter.py +0 -0
  113. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/no_org_assignment.py +0 -0
  114. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/operations/FEATURE.md +0 -0
  115. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/operations/MODULE_README.md +0 -0
  116. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/operations/__init__.py +0 -0
  117. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/operations/bulk_update_values.py +0 -0
  118. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/operations/catalog_select.py +0 -0
  119. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/operations/conflict.py +0 -0
  120. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/operations/conflict_writes.py +0 -0
  121. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/operations/create.py +0 -0
  122. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/operations/db_functions.py +0 -0
  123. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/operations/delete.py +0 -0
  124. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/operations/dynamic_admin.py +0 -0
  125. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/operations/dynamic_crud.py +0 -0
  126. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/operations/graph_walk.py +0 -0
  127. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/operations/latest_rows.py +0 -0
  128. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/operations/queue_claim.py +0 -0
  129. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/operations/read.py +0 -0
  130. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/operations/relational_backfill.py +0 -0
  131. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/operations/staging_load.py +0 -0
  132. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/operations/update.py +0 -0
  133. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/platform_access.py +0 -0
  134. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/platform_db.py +0 -0
  135. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/py.typed +0 -0
  136. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/pytest_live_db_guard.py +0 -0
  137. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/pytest_plugin.py +0 -0
  138. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/python_sql/MODULE_README.md +0 -0
  139. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/python_sql/__init__.py +0 -0
  140. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/python_sql/db_objects.py +0 -0
  141. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/python_sql/table_detailed_relationships.py +0 -0
  142. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/python_sql/table_typescript_relationship.py +0 -0
  143. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/query/FEATURE.md +0 -0
  144. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/query/MODULE_README.md +0 -0
  145. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/query/__init__.py +0 -0
  146. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/query/builder.py +0 -0
  147. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/query/executor.py +0 -0
  148. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/resources/__init__.py +0 -0
  149. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/resources/additional-assigners-2026-09-12.json +0 -0
  150. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/resources/org_assignment_trigger_baseline.json +0 -0
  151. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/schema_builder/FEATURE.md +0 -0
  152. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/schema_builder/MODULE_README.md +0 -0
  153. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/schema_builder/__init__.py +0 -0
  154. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/schema_builder/code_handler.py +0 -0
  155. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/schema_builder/codegen_writer.py +0 -0
  156. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/schema_builder/columns.py +0 -0
  157. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/schema_builder/common.py +0 -0
  158. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/schema_builder/diff_preview.py +0 -0
  159. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/schema_builder/entity_capabilities.py +0 -0
  160. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/schema_builder/generator.py +0 -0
  161. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/schema_builder/helpers/__init__.py +0 -0
  162. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/schema_builder/helpers/base_generators.py +0 -0
  163. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/schema_builder/helpers/entity_generators.py +0 -0
  164. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/schema_builder/helpers/git_checker.py +0 -0
  165. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/schema_builder/package_wiring.py +0 -0
  166. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/schema_builder/relationships.py +0 -0
  167. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/schema_builder/runner.py +0 -0
  168. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/schema_builder/schema.py +0 -0
  169. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/schema_builder/schema_manager.py +0 -0
  170. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/schema_builder/tables.py +0 -0
  171. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/schema_builder/views.py +0 -0
  172. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/secrets_battery/FEATURE.md +0 -0
  173. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/secrets_battery/__init__.py +0 -0
  174. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/secrets_battery/attachments.py +0 -0
  175. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/secrets_battery/backup_restore.py +0 -0
  176. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/secrets_battery/crypto.py +0 -0
  177. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/secrets_battery/host_authorization.py +0 -0
  178. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/secrets_battery/items.py +0 -0
  179. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/secrets_battery/model.py +0 -0
  180. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/secrets_battery/mutation_receipts.py +0 -0
  181. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/secrets_battery/native_enable.py +0 -0
  182. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/secrets_battery/native_lifecycle.py +0 -0
  183. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/secrets_battery/native_passkeys.py +0 -0
  184. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/secrets_battery/native_source.py +0 -0
  185. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/secrets_battery/org_service.py +0 -0
  186. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/secrets_battery/org_vault_gate.py +0 -0
  187. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/secrets_battery/service.py +0 -0
  188. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/secrets_battery/vault_history.py +0 -0
  189. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/session/README.md +0 -0
  190. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/session/__init__.py +0 -0
  191. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/session/coalesce.py +0 -0
  192. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/session/dag.py +0 -0
  193. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/session/errors.py +0 -0
  194. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/session/fallback.py +0 -0
  195. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/session/lifecycle.py +0 -0
  196. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/session/managed.py +0 -0
  197. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/session/op.py +0 -0
  198. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/session/reads.py +0 -0
  199. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/session/session.py +0 -0
  200. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/session/telemetry.py +0 -0
  201. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/sql_executor/MODULE_README.md +0 -0
  202. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/sql_executor/__init__.py +0 -0
  203. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/sql_executor/executor.py +0 -0
  204. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/sql_executor/queries.py +0 -0
  205. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/sql_executor/registry.py +0 -0
  206. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/sql_executor/types.py +0 -0
  207. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/sql_executor/utils.py +0 -0
  208. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/state.py +0 -0
  209. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/utils/__init__.py +0 -0
  210. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/utils/sql_utils.py +0 -0
  211. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/matrx_orm/utils/type_converters.py +0 -0
  212. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/scripts/git-branches.sh +0 -0
  213. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/scripts/publish.sh +0 -0
  214. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/scripts/release.sh +0 -0
  215. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/MODULE_README.md +0 -0
  216. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/__init__.py +0 -0
  217. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/conftest.py +0 -0
  218. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/MODULE_README.md +0 -0
  219. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/__init__.py +0 -0
  220. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_adapter_registry.py +0 -0
  221. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_admin_db_columns.py +0 -0
  222. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_admin_response_rendering.py +0 -0
  223. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_admin_search_negation.py +0 -0
  224. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_admin_search_sentinels.py +0 -0
  225. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_advisory_lock.py +0 -0
  226. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_agent_message.py +0 -0
  227. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_api_auth.py +0 -0
  228. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_api_config.py +0 -0
  229. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_api_handlers.py +0 -0
  230. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_api_protocol.py +0 -0
  231. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_array_agg_order_by.py +0 -0
  232. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_associations.py +0 -0
  233. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_associations_wiring.py +0 -0
  234. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_bulk_hydration_offload.py +0 -0
  235. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_bulk_update_by_pk.py +0 -0
  236. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_bulk_upsert_increment_set_fields.py +0 -0
  237. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_cache_debug.py +0 -0
  238. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_call_function.py +0 -0
  239. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_case_when.py +0 -0
  240. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_catalog.py +0 -0
  241. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_column_scoped_writes.py +0 -0
  242. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_committed_row_guard.py +0 -0
  243. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_composite_pk_filter.py +0 -0
  244. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_composite_pk_write_paths.py +0 -0
  245. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_config.py +0 -0
  246. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_conflict_target.py +0 -0
  247. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_conflict_writes.py +0 -0
  248. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_connection_codecs.py +0 -0
  249. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_connection_poison_guard.py +0 -0
  250. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_connection_primitives.py +0 -0
  251. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_count_composite_distinct.py +0 -0
  252. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_count_limit_param_leak.py +0 -0
  253. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_db_function_field.py +0 -0
  254. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_ddl_generator.py +0 -0
  255. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_deadlock_retry.py +0 -0
  256. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_derived_table.py +0 -0
  257. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_dirty_tracking.py +0 -0
  258. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_dynamic_crud.py +0 -0
  259. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_exceptions.py +0 -0
  260. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_execute_query_integrity_mapping.py +0 -0
  261. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_expression_primitives.py +0 -0
  262. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_fields.py +0 -0
  263. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_filter_jsonb_agg_array_index.py +0 -0
  264. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_fk_cycle_resolution.py +0 -0
  265. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_from_alias.py +0 -0
  266. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_fts.py +0 -0
  267. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_func_expression_args.py +0 -0
  268. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_graph_walk.py +0 -0
  269. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_having_annotate_alias.py +0 -0
  270. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_insert_ignore_and_admin_primitives.py +0 -0
  271. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_introspect_rls_policies.py +0 -0
  272. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_join_and_aggregate.py +0 -0
  273. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_latest_rows.py +0 -0
  274. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_listen_notify.py +0 -0
  275. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_local_sqlite.py +0 -0
  276. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_lock_contention_retry.py +0 -0
  277. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_managed_write_guard.py +0 -0
  278. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_matrx_entity.py +0 -0
  279. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_migration_diff_types.py +0 -0
  280. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_migration_loader.py +0 -0
  281. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_model_instance.py +0 -0
  282. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_model_meta.py +0 -0
  283. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_optimistic_concurrency.py +0 -0
  284. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_order_by_expression.py +0 -0
  285. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_org_assignment_inventory.py +0 -0
  286. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_orm_banners_are_client_safe.py +0 -0
  287. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_parameter_name_hydration.py +0 -0
  288. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_patch_jsonb_path.py +0 -0
  289. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_percentile_cont.py +0 -0
  290. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_persisted_count.py +0 -0
  291. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_person_scoped_session_bypasses_shared_cache.py +0 -0
  292. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_pool_create_never_wedges.py +0 -0
  293. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_pool_holder_census.py +0 -0
  294. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_pool_loop_recreation.py +0 -0
  295. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_pool_maintenance.py +0 -0
  296. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_postgrest_filters.py +0 -0
  297. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_pydantic_bridge.py +0 -0
  298. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_query_builder.py +0 -0
  299. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_query_error_detail.py +0 -0
  300. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_query_executor_sql.py +0 -0
  301. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_query_timeout.py +0 -0
  302. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_querybuilder_clone_semantics.py +0 -0
  303. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_queue_claim.py +0 -0
  304. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_read_connection_retry.py +0 -0
  305. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_registry.py +0 -0
  306. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_registry_multi_database.py +0 -0
  307. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_registry_multi_schema.py +0 -0
  308. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_relational_backfill.py +0 -0
  309. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_relations.py +0 -0
  310. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_resilience.py +0 -0
  311. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_schema_exists.py +0 -0
  312. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_session_advisory_lock.py +0 -0
  313. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_standalone_write_session_context.py +0 -0
  314. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_state_cache.py +0 -0
  315. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_subquery_filter_raw.py +0 -0
  316. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_subquery_in_filter.py +0 -0
  317. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_subquery_nested_expressions.py +0 -0
  318. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_supabase_auth.py +0 -0
  319. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_supabase_config.py +0 -0
  320. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_tls.py +0 -0
  321. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_typed_join_projection.py +0 -0
  322. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_typed_predicates.py +0 -0
  323. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_update_case_expression.py +0 -0
  324. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_update_subquery.py +0 -0
  325. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_update_with_rebase.py +0 -0
  326. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_upsert_default_leak.py +0 -0
  327. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_upsert_with_conflict.py +0 -0
  328. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_values_jsonb_decode.py +0 -0
  329. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_watched_lifecycle.py +0 -0
  330. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_write_queue_deadline.py +0 -0
  331. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level1/test_write_retry_semantics.py +0 -0
  332. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level2/MODULE_README.md +0 -0
  333. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level2/__init__.py +0 -0
  334. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level2/conftest.py +0 -0
  335. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level2/test_bulk_ops.py +0 -0
  336. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level2/test_cache_integration.py +0 -0
  337. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level2/test_crud.py +0 -0
  338. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level2/test_foreign_keys.py +0 -0
  339. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level2/test_m2m.py +0 -0
  340. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level2/test_manager.py +0 -0
  341. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level2/test_migrations_live.py +0 -0
  342. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level2/test_query_execution.py +0 -0
  343. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level2/test_schema_diff.py +0 -0
  344. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/level2/verify_committed_row_guard_pg17.py +0 -0
  345. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/live_pooler_role.py +0 -0
  346. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/native_source_cases.py +0 -0
  347. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/native_source_oracle.rs +0 -0
  348. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/sample_project/.env.example +0 -0
  349. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/sample_project/README.md +0 -0
  350. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/sample_project/__init__.py +0 -0
  351. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/sample_project/generate.py +0 -0
  352. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/sample_project/generated/.gitkeep +0 -0
  353. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/sample_project/matrx_orm.yaml +0 -0
  354. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/sample_project/test_schema_generation.py +0 -0
  355. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/sample_project_desktop/.env.example +0 -0
  356. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/sample_project_desktop/README.md +0 -0
  357. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/sample_project_desktop/__init__.py +0 -0
  358. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/sample_project_desktop/client_example.py +0 -0
  359. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/sample_project_desktop/client_example.ts +0 -0
  360. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/sample_project_desktop/client_supabase_example.py +0 -0
  361. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/sample_project_desktop/server.py +0 -0
  362. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/schema/entity_tests.py +0 -0
  363. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/schema/test_base_generation.py +0 -0
  364. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/schema/test_composite_pk_fk_generation.py +0 -0
  365. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/schema/test_composite_unique_generation.py +0 -0
  366. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/schema/test_dto_identity_generation.py +0 -0
  367. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/schema/test_entity_capability_generation.py +0 -0
  368. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/schema/test_enum_ordering.py +0 -0
  369. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/schema/test_generate_schema.py +0 -0
  370. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/schema/test_junction_analysis.py +0 -0
  371. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/schema/test_multi_schema_output.py +0 -0
  372. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/schema/test_schema_analysis.py +0 -0
  373. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/schema/test_sql_expression_defaults.py +0 -0
  374. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/schema/test_table_relationships_query.py +0 -0
  375. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/schema/test_view_generation.py +0 -0
  376. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/schema_builder/test_array_default_parsing.py +0 -0
  377. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/schema_builder/test_focused_generation_plan.py +0 -0
  378. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/schema_builder/test_host_models_aggregator.py +0 -0
  379. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/schema_builder/test_never_filter_columns.py +0 -0
  380. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/session/__init__.py +0 -0
  381. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/session/conftest.py +0 -0
  382. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/session/test_a_failed_capture_still_names_the_failure.py +0 -0
  383. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/session/test_cache_no_downgrade.py +0 -0
  384. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/session/test_capture_poison_proof.py +0 -0
  385. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/session/test_coalesce.py +0 -0
  386. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/session/test_dag.py +0 -0
  387. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/session/test_disk_spill_fallback.py +0 -0
  388. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/session/test_flush_individual_fallback.py +0 -0
  389. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/session/test_governed_write.py +0 -0
  390. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/session/test_live_db_guard_plugin.py +0 -0
  391. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/session/test_managed.py +0 -0
  392. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/session/test_op_enqueue_site.py +0 -0
  393. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/session/test_reads.py +0 -0
  394. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/session/test_record_error_bounded_insert.py +0 -0
  395. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/session/test_record_error_storm.py +0 -0
  396. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/session/test_session.py +0 -0
  397. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/session/test_spill_guard_plugin.py +0 -0
  398. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/test_array_arg_element_type.py +0 -0
  399. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/test_attachment_mutation_serialization.py +0 -0
  400. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/test_backup_alias_conflict.py +0 -0
  401. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/test_backup_history_restore.py +0 -0
  402. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/test_backup_restore_live.py +0 -0
  403. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/test_call_function_as.py +0 -0
  404. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/test_call_function_none_mode.py +0 -0
  405. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/test_composite_fk_is_not_a_column_fk.py +0 -0
  406. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/test_database_name_alias.py +0 -0
  407. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/test_db_functions.py +0 -0
  408. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/test_dsn_password_masking.py +0 -0
  409. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/test_dynamic_select_unregistered.py +0 -0
  410. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/test_immutable_insert_or_exact_verify.py +0 -0
  411. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/test_keyed_digest.py +0 -0
  412. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/test_model_cls_refactor.py +0 -0
  413. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/test_native_enable.py +0 -0
  414. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/test_native_lifecycle.py +0 -0
  415. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/test_native_lifecycle_fences.py +0 -0
  416. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/test_native_passkey_receipts.py +0 -0
  417. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/test_native_source.py +0 -0
  418. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/test_no_blocking_asyncpg_tls.py +0 -0
  419. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/test_one_database.py +0 -0
  420. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/test_order_by_desc_nulls_last.py +0 -0
  421. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/test_org_vault_gate.py +0 -0
  422. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/test_secret_audit_names_its_organization.py +0 -0
  423. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/test_secrets_battery.py +0 -0
  424. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/test_secrets_minting_keys.py +0 -0
  425. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/test_shared_lock_guard.py +0 -0
  426. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/test_sql_param_casts.py +0 -0
  427. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/test_statement_ceiling_warning.py +0 -0
  428. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/test_sync_twins_guard.py +0 -0
  429. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/test_update_result_truthiness.py +0 -0
  430. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/test_user_visible_lookup.py +0 -0
  431. {matrx_orm-3.1.199 → matrx_orm-3.1.201}/tests/test_vault_aggregate_recovery_stateful.py +0 -0
@@ -2,6 +2,11 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ - **Fix: the shared-lock sampler no longer leaves its timeouts on a pooled backend.**
6
+ `pytest_lock_guard` sent session-level `set lock_timeout` / `set statement_timeout`
7
+ through the transaction pooler, where they stay on the backend for the next client
8
+ (both found leaked on the clone's pool, 2026-10-01). It now uses `SET LOCAL` inside
9
+ each sample's own transaction.
5
10
  - **Fix: a driver timeout longer than the server's statement ceiling screams.**
6
11
  `call_function` / `call_function_as` / `execute_query` read the connection's
7
12
  effective `statement_timeout` and log one WARNING per call site when the
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: matrx-orm
3
- Version: 3.1.199
3
+ Version: 3.1.201
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
@@ -298,6 +298,7 @@ from .core.transaction import (
298
298
  advisory_unlock,
299
299
  advisory_xact_lock,
300
300
  set_local_guc,
301
+ set_local_gucs,
301
302
  transaction,
302
303
  try_advisory_lock,
303
304
  try_advisory_xact_lock,
@@ -697,6 +698,7 @@ __all__ = [
697
698
  "transaction",
698
699
  "acquire_connection",
699
700
  "set_local_guc",
701
+ "set_local_gucs",
700
702
  "SessionContextProvider",
701
703
  "ACTOR_AGENT_GUC",
702
704
  "ACTOR_SYSTEM_GUC",
@@ -261,6 +261,55 @@ pinned server session's identity before and after `RESET SESSION AUTHORIZATION`
261
261
  borrowed role. This also repairs both `SET ROLE` and `SET SESSION AUTHORIZATION`;
262
262
  `RESET ROLE` alone does not repair the latter.
263
263
 
264
+ **A clean, genuinely empty read is proved in ONE statement.** Before the pinned
265
+ re-proof, `prove_empty_in_one_statement` runs `current_user`, `session_user`, the
266
+ backend's login role (`pg_stat_activity.usename`, which `SET SESSION AUTHORIZATION`
267
+ does not change) and `EXISTS (<query>)` as one statement — one server connection,
268
+ one role. All three identities equal and no row: proven empty, one round trip
269
+ instead of seven. Any other outcome (borrowed role, session-authorization swap,
270
+ unreadable login, rows that appeared, a query that cannot embed, any error) runs
271
+ the full repairing re-proof unchanged. Pinned by `tests/test_pooler_role_guard.py`.
272
+
273
+ **Round trips are the launch cost on a remote pooler.** Every top-level BEGIN sets
274
+ its ceilings, the host's declared session context and the opener's own settings
275
+ (`transaction(opening_gucs=…)`; `rls_session` passes claims + timeouts + role,
276
+ role LAST) in ONE `set_config` statement; `set_local_gucs` does the same inside a
277
+ transaction. A transaction-mode pool releases connections with no reset statement
278
+ (`_transaction_pool_release`); a session-mode pool keeps asyncpg's full reset.
279
+ Measured on one agent launch against the clone through the pooler (170 ms per round
280
+ trip from a laptop): 435 → ~200 round trips. Pinned by
281
+ `tests/level1/test_transaction_gucs_one_round_trip.py`,
282
+ `tests/level1/test_transaction_pool_release_sends_nothing.py` and
283
+ `tests/level1/test_rls_session_scope.py`.
284
+
285
+ **A backend another client left read-only never refuses our writes.** A transaction
286
+ pooler shares server connections with every client of the same login and never resets
287
+ one between clients, so a session-level `SET default_transaction_read_only = on` (or
288
+ `SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY`) sent outside a transaction — the
289
+ shape of an ad-hoc "read-only production" helper — leaves that backend read-only for
290
+ everyone (2026-10-01 incident: dispatchers, workflow claims, request audit, chat writes;
291
+ recurring since 2026-09-12). A transaction-mode pool's connections are
292
+ `ReadWriteConnection` (`read_write.py`): every top-level BEGIN naming no access mode is
293
+ sent as `... READ WRITE` (zero extra round trips), and an autocommit statement refused as
294
+ read-only re-runs once inside `BEGIN READ WRITE` with a WARNING naming the cause. An
295
+ explicit `READ ONLY` is kept; the session is never rewritten. Read-only work through the
296
+ pooler belongs in `BEGIN READ ONLY`, never a session SET. Pinned by
297
+ `tests/test_a_read_only_backend_never_refuses_our_writes.py` (loopback `ISOLATED_PG17_DSN`)
298
+ and `tests/level1/test_read_write_declared.py`.
299
+
300
+ **A backend handed over inside another client's transaction never takes our writes.** The
301
+ second shape of the same incident: a probe sent `begin read only; …; rollback;` through the
302
+ pooler, an error or timeout skipped the `rollback`, and the pooler handed the open READ ONLY
303
+ transaction to the server. The pooler answers our handshake "idle", so asyncpg learns of it
304
+ only from the reply. A statement that comes back inside a transaction this connection never
305
+ opened is the stranger's: rolled back, logged at ERROR with the remedy, re-run once in our own
306
+ `BEGIN READ WRITE … COMMIT` (the rollback undid it, so exactly once). A top-level BEGIN answered
307
+ with 25001 is the same: rolled back and begun again; a second stranger raises
308
+ `InheritedTransactionError` by name. Pinned by
309
+ `tests/test_a_backend_handed_over_mid_transaction_never_takes_our_writes.py`, which runs an
310
+ in-process transaction pooler that never resets its one server connection (loopback
311
+ `ISOLATED_PG17_DSN`; 4 of 5 fail on the code before it).
312
+
264
313
  **The destructive live proof is loopback-only.**
265
314
  `tests/live_pooler_role.py::assert_isolated_pooler_target` runs before DSN
266
315
  construction or connection and refuses every shared or remote database. The proof
@@ -339,6 +388,9 @@ can never disagree about who the blocker was.
339
388
  Verified against the live database: 2026-08-21.
340
389
 
341
390
  ## Change log
391
+ - 2026-10-01: `ReadWriteConnection` rolls back a stranger's open transaction it was handed (statement reply or 25001 on BEGIN), re-runs our statement in its own transaction, and names a repeat `InheritedTransactionError`.
392
+ - 2026-10-01: `ReadWriteConnection` for transaction-mode pools — every BEGIN declares READ WRITE and a read-only-refused autocommit statement re-runs once in one; a pooled backend another client left read-only at session level no longer fails the platform's writes.
393
+ - 2026-09-30: Fewer round trips per request (agent-launch latency, lane deep): one-statement proof of a clean empty read; ceilings + declared context + `rls_session` settings in ONE BEGIN-time `set_config` statement (`transaction(opening_gucs=)`, `set_local_gucs`, `emit_local_gucs`); transaction-mode pools release with no reset statement. 435 → ~200 round trips on a flashcard launch.
342
394
  - 2026-09-30: `statement_ceiling.py` — a driver timeout above the server's statement ceiling is logged once per call site; `SERVER_CEILING` sentinel. ~19 callers passed 60-600 s under the 30 s `postgres` ceiling (the nightly keyword classifier was cancelled at 30 s for eight days).
343
395
  - 2026-09-12 (later): ONE retry mechanism. Two independent lock-retry
344
396
  implementations for the same class landed hours apart — a `LockContentionError`
@@ -25,6 +25,7 @@ from matrx_orm.core.pooler_role import (
25
25
  reprove_empty_read,
26
26
  )
27
27
  from matrx_orm.core.statement_ceiling import resolve_driver_timeout
28
+ from matrx_orm.core.read_write import ReadWriteConnection
28
29
  from matrx_orm.core.tls import resolve_asyncpg_ssl
29
30
  from matrx_orm.exceptions import (
30
31
  AdapterError,
@@ -421,6 +422,25 @@ class ConnectionIdentity:
421
422
  return f"{cluster}/{self.database}"
422
423
 
423
424
 
425
+ async def _transaction_pool_release(_conn: Any) -> None:
426
+ """Release a transaction-pooler connection with NO session reset statement.
427
+
428
+ asyncpg's default release runs ``pg_advisory_unlock_all(); CLOSE ALL;
429
+ UNLISTEN *; RESET ALL`` — one round trip on EVERY release. Behind a
430
+ transaction-mode pooler (Supavisor / PgBouncer, port 6543) that statement
431
+ lands on whichever server connection the pooler picks, so it cannot reset
432
+ anything the next statement from this client will see: there is no session
433
+ state of ours to reset (every GUC here is ``SET LOCAL``, LISTEN rides its own
434
+ session connection, cursors die with their transaction). It cost ~88 of the
435
+ ~435 round trips of one agent launch (measured 2026-09-30, ~15 s at a
436
+ laptop's 170 ms). asyncpg still rolls back an open transaction before this
437
+ hook (``Connection._reset``), and a session-mode pool keeps the full default
438
+ reset. ``RESET ALL`` never reset a borrowed ROLE either (``role`` is
439
+ ``GUC_NO_RESET_ALL``) — that defence is ``pooler_role``.
440
+ """
441
+ return None
442
+
443
+
424
444
  class AsyncDatabaseManager:
425
445
  _instance = None
426
446
  _pools = {}
@@ -669,6 +689,20 @@ class AsyncDatabaseManager:
669
689
  server_settings=server_settings,
670
690
  init=_init_connection_default,
671
691
  timeout=connect_timeout,
692
+ reset=(
693
+ _transaction_pool_release
694
+ if effective_mode == "transaction"
695
+ else None
696
+ ),
697
+ # A transaction pooler hands us server connections other
698
+ # clients may have left read-only at SESSION level; every
699
+ # BEGIN declares READ WRITE and a refused autocommit
700
+ # statement re-runs once in one (core/read_write.py).
701
+ connection_class=(
702
+ ReadWriteConnection
703
+ if effective_mode == "transaction"
704
+ else asyncpg.Connection
705
+ ),
672
706
  )
673
707
  try:
674
708
  async with asyncio.timeout(create_timeout):
@@ -223,6 +223,61 @@ async def emit_contamination(event: EmptyReadContaminationEvent) -> None:
223
223
  logger.exception("[pooler-role] contamination sink raised")
224
224
 
225
225
 
226
+ def _single_statement_proof_sql(query: str) -> str | None:
227
+ """The one-statement absence proof for ``query``, or ``None`` if it cannot embed.
228
+
229
+ One statement executes on ONE server connection, so the identity columns and
230
+ the ``EXISTS`` are evaluated by the same backend under the same role. The
231
+ login column comes from ``pg_stat_activity`` (the role the backend
232
+ authenticated as), which ``SET SESSION AUTHORIZATION`` does not change — so a
233
+ session-authorization swap, where ``current_user`` and ``session_user`` agree
234
+ on the WRONG identity, still fails the proof.
235
+ """
236
+ body = query.strip().rstrip(";").strip()
237
+ if not body or ";" in body:
238
+ return None
239
+ return (
240
+ "SELECT current_user AS current_user_, session_user AS session_user_, "
241
+ "(SELECT usename FROM pg_catalog.pg_stat_activity WHERE pid = pg_backend_pid()) "
242
+ "AS login_user_, "
243
+ f"EXISTS ({body}) AS has_rows_"
244
+ )
245
+
246
+
247
+ async def prove_empty_in_one_statement(
248
+ conn: Any,
249
+ *,
250
+ query: str,
251
+ args: tuple[Any, ...],
252
+ statement_timeout: float | None,
253
+ ) -> bool:
254
+ """True only when ONE statement proves the read is genuinely empty.
255
+
256
+ The fast path of Layer 2. A clean session (``current_user`` = ``session_user``
257
+ = the backend's login role) that finds no row IS the proof the full re-proof
258
+ would reach — in one round trip instead of seven (BEGIN, two identity reads,
259
+ two resets, the re-read, COMMIT). Every other outcome — a borrowed role, a
260
+ session-authorization swap, rows that appeared since, a query that cannot be
261
+ embedded, any error — returns False and the full repairing re-proof runs, so
262
+ no contaminated or unprovable read ever short-circuits here.
263
+ """
264
+ sql = _single_statement_proof_sql(query)
265
+ if sql is None:
266
+ return False
267
+ try:
268
+ row = await conn.fetchrow(sql, *args, timeout=statement_timeout)
269
+ except Exception: # noqa: BLE001 — any failure means "not proven"; the full path decides
270
+ return False
271
+ if row is None:
272
+ return False
273
+ current = row["current_user_"]
274
+ session = row["session_user_"]
275
+ login = row["login_user_"]
276
+ if not current or current != session or current != login:
277
+ return False
278
+ return row["has_rows_"] is False
279
+
280
+
226
281
  async def reprove_empty_read(
227
282
  conn: Any,
228
283
  *,
@@ -238,10 +293,19 @@ async def reprove_empty_read(
238
293
  the read was genuinely empty. Raises ``PoolerRoleContamination`` when a clean
239
294
  session cannot be obtained at all — an unprovable absence is never a result.
240
295
 
296
+ The common case — a genuinely empty read on a clean pool — is settled by
297
+ ``prove_empty_in_one_statement`` in one round trip. Anything it cannot prove
298
+ falls through to the pinned, repairing re-proof below.
299
+
241
300
  ``conn`` must be a connection not currently inside a transaction; the explicit
242
301
  transaction opened here is what pins every statement below to ONE server
243
302
  connection, which is the only thing that makes ``RESET ROLE`` meaningful.
244
303
  """
304
+ if await prove_empty_in_one_statement(
305
+ conn, query=query, args=args, statement_timeout=statement_timeout
306
+ ):
307
+ return None
308
+
245
309
  last_role: tuple[str, str] | None = None
246
310
  for _ in range(REPROVE_ATTEMPTS):
247
311
  async with conn.transaction():
@@ -0,0 +1,221 @@
1
+ """A pooled backend another client left read-only never refuses our writes.
2
+
3
+ A transaction-mode pooler (Supavisor / PgBouncer, port 6543) shares its server
4
+ connections between EVERY client that logs in as the same user, and never resets
5
+ one between clients. A client that runs a session-level read-only setting outside a
6
+ transaction — ``SET default_transaction_read_only = on``,
7
+ ``SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY`` (the shape of an ad-hoc
8
+ "read-only production" helper) — leaves it on whichever server connection the pooler
9
+ handed it. Every later transaction routed there starts read-only, and every write
10
+ fails with ``cannot execute ... in a read-only transaction`` while the database is
11
+ writable. (2026-10-01: SMS and notification dispatchers, workflow claims, the request
12
+ audit and chat writes; recurring since at least 2026-09-12. Proven live: one of 21
13
+ pooled backends carried ``default_transaction_read_only = on``, ``source = session``.)
14
+
15
+ ``ReadWriteConnection`` is the connection class of every transaction-mode pool:
16
+
17
+ * every top-level ``BEGIN`` that names no access mode is sent as ``BEGIN ... READ
18
+ WRITE`` — the ORM's own ``transaction()`` and any raw ``conn.transaction()``
19
+ alike, at no extra round trip. An explicit ``READ ONLY`` is kept.
20
+ * an AUTOCOMMIT statement the backend refuses as read-only (SQLSTATE 25006) is re-run
21
+ once inside one explicit ``BEGIN READ WRITE`` transaction, and the event is logged
22
+ with its remedy. The refusal proves the statement never ran: Postgres checks the
23
+ access mode before a statement modifies anything, and an implicit transaction
24
+ rolls back whole.
25
+
26
+ THE SECOND SHAPE — a server connection handed over INSIDE another client's transaction
27
+ (2026-10-01: live probes sent ``begin read only; …; rollback;`` as one string through the
28
+ pooler; an error or timeout skipped the ``rollback`` and the open READ ONLY transaction went to
29
+ the platform server). A transaction pooler answers our handshake with "idle" itself, so asyncpg
30
+ only learns of the stranger's transaction from the reply to our next statement:
31
+
32
+ * a statement that comes back inside a transaction this connection never opened (asyncpg's
33
+ ``_top_xact`` unset, no BEGIN of ours open) ran inside a STRANGER's transaction: that
34
+ transaction is rolled back, logged at ERROR with its remedy, and the statement re-runs once in
35
+ our own ``BEGIN READ WRITE … COMMIT``. The rollback undid whatever the statement did, so the
36
+ re-run is exactly once.
37
+ * a top-level ``BEGIN`` the server answers with SQLSTATE 25001 ("there is already a transaction
38
+ in progress" / "read-write mode must be set before any query") is the same stranger: rolled
39
+ back, begun again once; a second stranger raises ``InheritedTransactionError`` by name —
40
+ never a bare 25006 three statements later.
41
+
42
+ It never touches the session it was handed — the poison stays visible to whoever
43
+ investigates (``SHOW default_transaction_read_only``) and the guard stays per
44
+ transaction. Session-mode pools keep the plain asyncpg connection: their server
45
+ connection is ours alone and asyncpg's release reset clears it.
46
+ """
47
+
48
+ from __future__ import annotations
49
+
50
+ import logging
51
+ import re
52
+ from collections.abc import Awaitable, Callable
53
+ from typing import Any
54
+
55
+ import asyncpg
56
+
57
+ logger = logging.getLogger(__name__)
58
+
59
+ # One statement that opens a top-level transaction: BEGIN / BEGIN WORK / BEGIN
60
+ # TRANSACTION / START TRANSACTION, optionally with modes and one trailing ';'.
61
+ _BEGIN_STATEMENT = re.compile(
62
+ r"^\s*(?:BEGIN|START\s+TRANSACTION)\b(?P<modes>[^;]*?)\s*;?\s*$",
63
+ re.IGNORECASE | re.DOTALL,
64
+ )
65
+ _ACCESS_MODE = re.compile(r"\bREAD\s+(?:ONLY|WRITE)\b", re.IGNORECASE)
66
+ # A statement that ends the top-level transaction (not ROLLBACK TO / RELEASE a savepoint).
67
+ _END_STATEMENT = re.compile(
68
+ r"^\s*(?:COMMIT|END|ABORT|ROLLBACK)\b(?!\s+(?:TO|PREPARED)\b)(?!\s+(?:WORK|TRANSACTION)\s+TO\b)",
69
+ re.IGNORECASE,
70
+ )
71
+ _ACTIVE_SQL_TRANSACTION = "25001"
72
+
73
+
74
+ def declare_read_write(query: str) -> str:
75
+ """``query`` with ``READ WRITE`` declared when it is a BEGIN naming no access mode.
76
+
77
+ Anything else — a savepoint, a multi-statement script, a BEGIN that already says
78
+ ``READ ONLY`` or ``READ WRITE`` — is returned unchanged.
79
+ """
80
+ match = _BEGIN_STATEMENT.match(query)
81
+ if match is None or _ACCESS_MODE.search(match.group("modes")):
82
+ return query
83
+ return query.strip().rstrip(";").rstrip() + " READ WRITE"
84
+
85
+
86
+ _REFUSED_REMEDY = (
87
+ "[matrx_orm] A pooled server connection refused an autocommit statement as "
88
+ "read-only: another client of the transaction pooler left a SESSION-level "
89
+ "read-only default on it (SET default_transaction_read_only / SET SESSION "
90
+ "CHARACTERISTICS AS TRANSACTION READ ONLY sent outside a transaction). The "
91
+ "statement never ran and was re-run inside BEGIN READ WRITE. Remedy: find the "
92
+ "client sending that SET through the pooler — read-only work belongs in "
93
+ "BEGIN READ ONLY, one transaction at a time. Statement: %s"
94
+ )
95
+
96
+
97
+ _STRANGER_REMEDY = (
98
+ "[matrx_orm] The pooler handed this connection a server connection still inside "
99
+ "another client's open transaction (a client of the transaction pooler left without "
100
+ "COMMIT/ROLLBACK, e.g. a 'begin read only; ...; rollback;' probe that errored or timed "
101
+ "out). That transaction was rolled back and our statement re-runs in its own "
102
+ "transaction. Remedy: find the client — probes belong on a DIRECT connection (:5432) "
103
+ "with ROLLBACK in a finally. Statement: %s"
104
+ )
105
+
106
+
107
+ class InheritedTransactionError(RuntimeError):
108
+ """Every server connection the pooler offered was inside another client's transaction."""
109
+
110
+
111
+ class ReadWriteConnection(asyncpg.Connection): # type: ignore[misc]
112
+ """The connection class of a transaction-mode pool (see module docstring)."""
113
+
114
+ _matrx_own_xact = False # a top-level BEGIN of ours is open
115
+ _matrx_saw_25001 = False # the server warned "already a transaction in progress"
116
+
117
+ def _process_log_message(self, fields: Any, last_query: Any) -> None:
118
+ if fields.get("C") == _ACTIVE_SQL_TRANSACTION:
119
+ self._matrx_saw_25001 = True
120
+ super()._process_log_message(fields, last_query)
121
+
122
+ async def _reset(self) -> None:
123
+ self._matrx_own_xact = False
124
+ await super()._reset()
125
+
126
+ def _in_strangers_transaction(self) -> bool:
127
+ return self.is_in_transaction() and self._top_xact is None and not self._matrx_own_xact
128
+
129
+ async def _drop_strangers_transaction(self, query: str) -> None:
130
+ logger.error(_STRANGER_REMEDY, " ".join(str(query).split())[:160])
131
+ self._matrx_own_xact = False
132
+ await asyncpg.Connection.execute(self, "ROLLBACK")
133
+
134
+ async def _begin(self, query: str) -> str:
135
+ """A top-level BEGIN that never silently joins a stranger's transaction."""
136
+ if self._in_strangers_transaction():
137
+ await self._drop_strangers_transaction(query)
138
+ for _ in range(2):
139
+ self._matrx_saw_25001 = False
140
+ try:
141
+ result = await asyncpg.Connection.execute(self, query)
142
+ except asyncpg.exceptions.ActiveSQLTransactionError:
143
+ pass
144
+ else:
145
+ if not self._matrx_saw_25001:
146
+ self._matrx_own_xact = True
147
+ return result
148
+ await self._drop_strangers_transaction(query)
149
+ raise InheritedTransactionError(
150
+ "Two server connections in a row were inside another client's open transaction; "
151
+ "nothing was written. A client of the transaction pooler is leaving transactions "
152
+ "open (see matrx_orm/core/read_write.py)."
153
+ )
154
+
155
+ async def _in_own_transaction(
156
+ self, call: Callable[..., Awaitable[Any]], query: str, args: tuple[Any, ...], kwargs: dict[str, Any]
157
+ ) -> Any:
158
+ await self._begin("BEGIN READ WRITE")
159
+ try:
160
+ result = await call(query, *args, **kwargs)
161
+ except BaseException:
162
+ self._matrx_own_xact = False
163
+ try:
164
+ await asyncpg.Connection.execute(self, "ROLLBACK")
165
+ except Exception: # noqa: BLE001 — the pool's release rolls back what this cannot
166
+ pass
167
+ raise
168
+ self._matrx_own_xact = False
169
+ await asyncpg.Connection.execute(self, "COMMIT")
170
+ return result
171
+
172
+ async def _healed(
173
+ self, call: Callable[..., Awaitable[Any]], query: str, args: tuple[Any, ...], kwargs: dict[str, Any]
174
+ ) -> Any:
175
+ if self._in_strangers_transaction():
176
+ await self._drop_strangers_transaction(query)
177
+ if self.is_in_transaction():
178
+ result = await call(query, *args, **kwargs)
179
+ if self._top_xact is None and _END_STATEMENT.match(str(query)):
180
+ self._matrx_own_xact = False
181
+ return result
182
+ try:
183
+ result = await call(query, *args, **kwargs)
184
+ except asyncpg.exceptions.ReadOnlySQLTransactionError:
185
+ if self.is_in_transaction():
186
+ await self._drop_strangers_transaction(query)
187
+ else:
188
+ logger.warning(_REFUSED_REMEDY, " ".join(str(query).split())[:160])
189
+ except Exception:
190
+ if not self._in_strangers_transaction():
191
+ raise
192
+ await self._drop_strangers_transaction(query)
193
+ else:
194
+ if not self._in_strangers_transaction():
195
+ return result
196
+ # The reply came from inside a stranger's transaction; rolling it back undid it.
197
+ await self._drop_strangers_transaction(query)
198
+ return await self._in_own_transaction(call, query, args, kwargs)
199
+
200
+ async def execute(self, query: str, *args: Any, **kwargs: Any) -> str:
201
+ if not args:
202
+ declared = declare_read_write(query)
203
+ if _BEGIN_STATEMENT.match(declared) and not (self.is_in_transaction() and not self._in_strangers_transaction()):
204
+ return await self._begin(declared)
205
+ query = declared
206
+ return await self._healed(super().execute, query, args, kwargs)
207
+
208
+ async def fetch(self, query: str, *args: Any, **kwargs: Any) -> list[Any]:
209
+ return await self._healed(super().fetch, query, args, kwargs)
210
+
211
+ async def fetchrow(self, query: str, *args: Any, **kwargs: Any) -> Any:
212
+ return await self._healed(super().fetchrow, query, args, kwargs)
213
+
214
+ async def fetchval(self, query: str, *args: Any, **kwargs: Any) -> Any:
215
+ return await self._healed(super().fetchval, query, args, kwargs)
216
+
217
+ async def executemany(self, command: str, args: Any, **kwargs: Any) -> None:
218
+ return await self._healed(super().executemany, command, (args,), kwargs)
219
+
220
+
221
+ __all__ = ["InheritedTransactionError", "ReadWriteConnection", "declare_read_write"]
@@ -115,43 +115,34 @@ async def rls_session(
115
115
 
116
116
  claims_json = json.dumps(claims, separators=(",", ":"))
117
117
 
118
- async with transaction(database=database):
118
+ # Set the verified claims FIRST (while still the privileged role), then drop to
119
+ # the restricted role. Order mirrors PostgREST; all of them are transaction-local
120
+ # (`set_config(..., true)` IS `SET LOCAL`) and survive the role switch within the
121
+ # same transaction.
122
+ #
123
+ # ONE ROUND TRIP FOR THE WHOLE OPENING. 2026-09-19 folded these four settings
124
+ # into one statement (0.437 s -> 0.192 s against the main database); 2026-09-30
125
+ # folded that statement into the BEGIN-time one that already carries the
126
+ # ceilings and the declared session context (`transaction(opening_gucs=...)`),
127
+ # saving one more round trip on every RLS session. `role` stays LAST — the
128
+ # opening statement always emits it last — so the claims land before the role
129
+ # drops, and the role still changes here and nowhere else.
130
+ gucs: dict[str, str] = {"request.jwt.claims": claims_json}
131
+ if statement_timeout_ms is not None:
132
+ gucs["statement_timeout"] = str(int(statement_timeout_ms))
133
+ if lock_timeout_ms is not None:
134
+ gucs["lock_timeout"] = str(int(lock_timeout_ms))
135
+ if idle_in_transaction_timeout_ms is not None:
136
+ gucs["idle_in_transaction_session_timeout"] = str(int(idle_in_transaction_timeout_ms))
137
+ gucs["role"] = role
138
+
139
+ async with transaction(database=database, opening_gucs=gucs):
119
140
  conn = get_active_connection()
120
141
  if conn is None: # pragma: no cover - transaction always sets it on first entry
121
142
  raise DatabaseError(
122
143
  message="rls_session: no active connection after opening transaction",
123
144
  details={"role": role},
124
145
  )
125
- # Set the verified claims FIRST (while still the privileged role), then drop to
126
- # the restricted role. Order mirrors PostgREST; all of them are transaction-local
127
- # (`set_config(..., true)` IS `SET LOCAL`) and survive the role switch within the
128
- # same transaction.
129
- #
130
- # ONE ROUND TRIP, NOT FOUR. These were four sequential `execute` calls, and a
131
- # round trip to a hosted pooler is not free: measured 2026-09-19 against the main
132
- # database, the three that always run cost 0.247 + 0.085 + 0.105 = 0.437 s
133
- # separately and 0.192 s as one statement — so every RLS session on the platform
134
- # paid a quarter of a second to say three things it could say at once. They are
135
- # emitted in the SAME ORDER inside one target list, which is the order that
136
- # matters: the claims land before the role drops. `SET LOCAL ROLE x` and
137
- # `set_config('role', 'x', true)` are the same GUC write, so the role still
138
- # changes here and nowhere else, and `role` stays LAST.
139
- settings: list[str] = ["set_config('request.jwt.claims', $1, true)"]
140
- bound: list[str] = [claims_json]
141
- if statement_timeout_ms is not None:
142
- bound.append(str(int(statement_timeout_ms)))
143
- settings.append(f"set_config('statement_timeout', ${len(bound)}, true)")
144
- if lock_timeout_ms is not None:
145
- bound.append(str(int(lock_timeout_ms)))
146
- settings.append(f"set_config('lock_timeout', ${len(bound)}, true)")
147
- if idle_in_transaction_timeout_ms is not None:
148
- bound.append(str(int(idle_in_transaction_timeout_ms)))
149
- settings.append(
150
- f"set_config('idle_in_transaction_session_timeout', ${len(bound)}, true)"
151
- )
152
- bound.append(role)
153
- settings.append(f"set_config('role', ${len(bound)}, true)")
154
- await conn.execute("select " + ", ".join(settings), *bound)
155
146
  # RLS decides every read in this block, so the process-wide by-id cache
156
147
  # (keyed by primary key alone) is bypassed both ways — see
157
148
  # ``transaction.in_person_scoped_session``.
@@ -87,10 +87,41 @@ def session_context_gucs() -> dict[str, str]:
87
87
  return out
88
88
 
89
89
 
90
+ def local_gucs_statement(gucs: Mapping[str, str]) -> tuple[str, tuple[str, ...]] | None:
91
+ """ONE statement that sets every GUC in ``gucs`` transaction-locally.
92
+
93
+ ``None`` when there is nothing to set. Names are validated like
94
+ ``set_local_guc`` validates them; values travel as bind parameters.
95
+ """
96
+ if not gucs:
97
+ return None
98
+ targets: list[str] = []
99
+ args: list[str] = []
100
+ for name, value in gucs.items():
101
+ if not isinstance(name, str) or not _GUC_NAME_RE.match(name):
102
+ raise ValueError(f"invalid transaction-local GUC name: {name!r}")
103
+ targets.append(f"set_config(${len(args) + 1}, ${len(args) + 2}, true)")
104
+ args.extend((name, "" if value is None else str(value)))
105
+ return "SELECT " + ", ".join(targets), tuple(args)
106
+
107
+
108
+ async def emit_local_gucs(connection: Any, gucs: Mapping[str, str]) -> None:
109
+ """Set every GUC in ``gucs`` on ``connection`` (inside a BEGIN) in ONE round trip.
110
+
111
+ One ``set_config`` statement per GUC cost a round trip each on EVERY top-level
112
+ transaction — three per BEGIN once the actor declaration landed, ~45 per agent
113
+ launch, ~7.5 s at a laptop's 170 ms to the pooler (measured 2026-09-30).
114
+ """
115
+ statement = local_gucs_statement(gucs)
116
+ if statement is None:
117
+ return
118
+ sql, args = statement
119
+ await connection.execute(sql, *args)
120
+
121
+
90
122
  async def apply_session_context(connection: Any) -> None:
91
123
  """Emit the declared GUCs on ``connection`` (already inside a BEGIN)."""
92
- for name, value in session_context_gucs().items():
93
- await connection.execute("SELECT set_config($1, $2, true)", name, value)
124
+ await emit_local_gucs(connection, session_context_gucs())
94
125
 
95
126
 
96
127
  # ---------------------------------------------------------------------------
@@ -256,6 +287,8 @@ __all__ = [
256
287
  "ActorTier",
257
288
  "SessionContextProvider",
258
289
  "apply_session_context",
290
+ "emit_local_gucs",
291
+ "local_gucs_statement",
259
292
  "configure_session_context",
260
293
  "current_actor",
261
294
  "current_tier",