matrx-orm 3.1.241__tar.gz → 3.1.243__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 (448) hide show
  1. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/PKG-INFO +2 -2
  2. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/core/FEATURE.md +15 -8
  3. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/core/async_db_manager.py +59 -5
  4. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/core/pooler_role.py +173 -28
  5. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/query/executor.py +1 -1
  6. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/session/fallback.py +7 -3
  7. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/session/lifecycle.py +4 -2
  8. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/pyproject.toml +2 -2
  9. matrx_orm-3.1.243/tests/level1/test_guarded_read_never_reruns_empty.py +142 -0
  10. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_read_connection_retry.py +24 -0
  11. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/.gitignore +0 -0
  12. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/AGENTS.md +0 -0
  13. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/BASE_CLASS_METHODS.md +0 -0
  14. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/CHANGELOG.md +0 -0
  15. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/CLAUDE.md +0 -0
  16. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/MODEL_API.md +0 -0
  17. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/README.md +0 -0
  18. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/database/orm/extended/managers/ai_model_base.py +0 -0
  19. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/docs/migrations.md +0 -0
  20. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/FEATURE.md +0 -0
  21. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/MODULE_README.md +0 -0
  22. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/__init__.py +0 -0
  23. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/adapters/MODULE_README.md +0 -0
  24. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/adapters/__init__.py +0 -0
  25. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/adapters/async_postgresql.py +0 -0
  26. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/adapters/base_adapter.py +0 -0
  27. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/adapters/postgrest_client_adapter.py +0 -0
  28. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/adapters/supabase_adapter.py +0 -0
  29. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/admin/FEATURE.md +0 -0
  30. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/admin/__init__.py +0 -0
  31. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/admin/router.py +0 -0
  32. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/api/__init__.py +0 -0
  33. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/api/auth.py +0 -0
  34. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/api/config.py +0 -0
  35. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/api/handlers.py +0 -0
  36. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/api/protocol.py +0 -0
  37. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/api/server.py +0 -0
  38. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/associations.py +0 -0
  39. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/cache_debug.py +0 -0
  40. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/catalog.py +0 -0
  41. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/catalog_sql.py +0 -0
  42. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/client/MODULE_README.md +0 -0
  43. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/client/__init__.py +0 -0
  44. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/client/postgres_connection.py +0 -0
  45. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/client/postgrest.py +0 -0
  46. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/client/supabase_auth.py +0 -0
  47. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/client/supabase_config.py +0 -0
  48. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/client/supabase_manager.py +0 -0
  49. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/core/EXTENDED-TASK.md +0 -0
  50. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/core/MODULE_README.md +0 -0
  51. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/core/RELATIONS-TASKS.md +0 -0
  52. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/core/TASKS.md +0 -0
  53. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/core/__init__.py +0 -0
  54. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/core/admin_lane.py +0 -0
  55. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/core/base.py +0 -0
  56. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/core/config.py +0 -0
  57. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/core/connections.py +0 -0
  58. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/core/db_function.py +0 -0
  59. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/core/diagnostics.py +0 -0
  60. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/core/expressions.py +0 -0
  61. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/core/extended.py +0 -0
  62. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/core/fields.py +0 -0
  63. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/core/foreign_session_state.py +0 -0
  64. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/core/introspection.py +0 -0
  65. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/core/listen.py +0 -0
  66. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/core/loop_filters.py +0 -0
  67. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/core/model_dto.py +0 -0
  68. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/core/model_view.py +0 -0
  69. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/core/one_database.py +0 -0
  70. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/core/paginator.py +0 -0
  71. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/core/pool_maintenance.py +0 -0
  72. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/core/pool_watch.py +0 -0
  73. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/core/pydantic_bridge.py +0 -0
  74. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/core/read_write.py +0 -0
  75. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/core/registry.py +0 -0
  76. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/core/relations.py +0 -0
  77. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/core/resilience.py +0 -0
  78. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/core/rls_session.py +0 -0
  79. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/core/session_context.py +0 -0
  80. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/core/signals.py +0 -0
  81. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/core/statement_ceiling.py +0 -0
  82. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/core/tls.py +0 -0
  83. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/core/transaction.py +0 -0
  84. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/core/types.py +0 -0
  85. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/core/write_queue.py +0 -0
  86. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/core/write_retry.py +0 -0
  87. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/entity.py +0 -0
  88. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/error_handling.py +0 -0
  89. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/exceptions.py +0 -0
  90. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/extended/__init__.py +0 -0
  91. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/extended/app_error_handler.py +0 -0
  92. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/identity_rule.py +0 -0
  93. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/local_sqlite.py +0 -0
  94. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/middleware/__init__.py +0 -0
  95. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/middleware/base.py +0 -0
  96. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/migrations/MODULE_README.md +0 -0
  97. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/migrations/__init__.py +0 -0
  98. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/migrations/cli.py +0 -0
  99. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/migrations/ddl.py +0 -0
  100. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/migrations/diff.py +0 -0
  101. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/migrations/executor.py +0 -0
  102. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/migrations/integration.py +0 -0
  103. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/migrations/loader.py +0 -0
  104. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/migrations/operations.py +0 -0
  105. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/migrations/state.py +0 -0
  106. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/migrations/table_filter.py +0 -0
  107. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/no_org_assignment.py +0 -0
  108. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/operations/FEATURE.md +0 -0
  109. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/operations/MODULE_README.md +0 -0
  110. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/operations/__init__.py +0 -0
  111. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/operations/bulk_update_values.py +0 -0
  112. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/operations/catalog_select.py +0 -0
  113. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/operations/conflict.py +0 -0
  114. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/operations/conflict_writes.py +0 -0
  115. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/operations/create.py +0 -0
  116. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/operations/db_functions.py +0 -0
  117. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/operations/delete.py +0 -0
  118. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/operations/dynamic_admin.py +0 -0
  119. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/operations/dynamic_crud.py +0 -0
  120. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/operations/graph_walk.py +0 -0
  121. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/operations/latest_rows.py +0 -0
  122. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/operations/queue_claim.py +0 -0
  123. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/operations/read.py +0 -0
  124. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/operations/relational_backfill.py +0 -0
  125. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/operations/staging_load.py +0 -0
  126. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/operations/update.py +0 -0
  127. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/platform_access.py +0 -0
  128. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/platform_db.py +0 -0
  129. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/py.typed +0 -0
  130. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/pytest_lock_guard.py +0 -0
  131. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/pytest_plugin.py +0 -0
  132. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/python_sql/MODULE_README.md +0 -0
  133. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/python_sql/__init__.py +0 -0
  134. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/python_sql/db_objects.py +0 -0
  135. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/python_sql/table_detailed_relationships.py +0 -0
  136. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/python_sql/table_typescript_relationship.py +0 -0
  137. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/query/FEATURE.md +0 -0
  138. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/query/MODULE_README.md +0 -0
  139. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/query/__init__.py +0 -0
  140. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/query/builder.py +0 -0
  141. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/resources/__init__.py +0 -0
  142. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/resources/additional-assigners-2026-09-12.json +0 -0
  143. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/resources/org_assignment_trigger_baseline.json +0 -0
  144. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/schema_builder/FEATURE.md +0 -0
  145. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/schema_builder/MODULE_README.md +0 -0
  146. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/schema_builder/__init__.py +0 -0
  147. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/schema_builder/code_handler.py +0 -0
  148. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/schema_builder/codegen_writer.py +0 -0
  149. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/schema_builder/columns.py +0 -0
  150. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/schema_builder/common.py +0 -0
  151. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/schema_builder/diff_preview.py +0 -0
  152. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/schema_builder/entity_capabilities.py +0 -0
  153. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/schema_builder/excluded_targets.py +0 -0
  154. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/schema_builder/generator.py +0 -0
  155. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/schema_builder/helpers/__init__.py +0 -0
  156. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/schema_builder/helpers/base_generators.py +0 -0
  157. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/schema_builder/helpers/entity_generators.py +0 -0
  158. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/schema_builder/helpers/git_checker.py +0 -0
  159. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/schema_builder/package_wiring.py +0 -0
  160. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/schema_builder/relationships.py +0 -0
  161. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/schema_builder/runner.py +0 -0
  162. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/schema_builder/schema.py +0 -0
  163. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/schema_builder/schema_manager.py +0 -0
  164. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/schema_builder/tables.py +0 -0
  165. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/schema_builder/views.py +0 -0
  166. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/secrets_battery/FEATURE.md +0 -0
  167. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/secrets_battery/__init__.py +0 -0
  168. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/secrets_battery/attachments.py +0 -0
  169. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/secrets_battery/backup_restore.py +0 -0
  170. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/secrets_battery/crypto.py +0 -0
  171. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/secrets_battery/host_authorization.py +0 -0
  172. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/secrets_battery/items.py +0 -0
  173. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/secrets_battery/model.py +0 -0
  174. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/secrets_battery/mutation_receipts.py +0 -0
  175. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/secrets_battery/native_enable.py +0 -0
  176. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/secrets_battery/native_lifecycle.py +0 -0
  177. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/secrets_battery/native_passkeys.py +0 -0
  178. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/secrets_battery/native_source.py +0 -0
  179. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/secrets_battery/org_service.py +0 -0
  180. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/secrets_battery/org_vault_gate.py +0 -0
  181. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/secrets_battery/service.py +0 -0
  182. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/secrets_battery/vault_history.py +0 -0
  183. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/session/README.md +0 -0
  184. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/session/__init__.py +0 -0
  185. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/session/coalesce.py +0 -0
  186. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/session/dag.py +0 -0
  187. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/session/errors.py +0 -0
  188. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/session/flush.py +0 -0
  189. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/session/managed.py +0 -0
  190. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/session/op.py +0 -0
  191. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/session/reads.py +0 -0
  192. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/session/session.py +0 -0
  193. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/session/telemetry.py +0 -0
  194. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/sql_executor/MODULE_README.md +0 -0
  195. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/sql_executor/__init__.py +0 -0
  196. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/sql_executor/executor.py +0 -0
  197. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/sql_executor/queries.py +0 -0
  198. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/sql_executor/registry.py +0 -0
  199. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/sql_executor/types.py +0 -0
  200. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/sql_executor/utils.py +0 -0
  201. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/state.py +0 -0
  202. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/utils/__init__.py +0 -0
  203. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/utils/sql_utils.py +0 -0
  204. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/utils/type_converters.py +0 -0
  205. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/matrx_orm/write_actor.py +0 -0
  206. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/scripts/git-branches.sh +0 -0
  207. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/scripts/publish.sh +0 -0
  208. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/scripts/release.sh +0 -0
  209. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/MODULE_README.md +0 -0
  210. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/__init__.py +0 -0
  211. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/conftest.py +0 -0
  212. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/MODULE_README.md +0 -0
  213. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/__init__.py +0 -0
  214. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_adapter_registry.py +0 -0
  215. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_admin_db_columns.py +0 -0
  216. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_admin_response_rendering.py +0 -0
  217. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_admin_search_negation.py +0 -0
  218. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_admin_search_sentinels.py +0 -0
  219. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_advisory_lock.py +0 -0
  220. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_agent_message.py +0 -0
  221. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_api_auth.py +0 -0
  222. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_api_config.py +0 -0
  223. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_api_handlers.py +0 -0
  224. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_api_protocol.py +0 -0
  225. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_array_agg_order_by.py +0 -0
  226. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_association_trigger_actor_naming.py +0 -0
  227. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_associations.py +0 -0
  228. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_associations_wiring.py +0 -0
  229. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_borrowed_role_never_reaches_server_calls.py +0 -0
  230. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_bulk_hydration_offload.py +0 -0
  231. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_bulk_update_by_pk.py +0 -0
  232. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_bulk_upsert_increment_set_fields.py +0 -0
  233. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_cache_debug.py +0 -0
  234. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_call_function.py +0 -0
  235. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_case_when.py +0 -0
  236. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_catalog.py +0 -0
  237. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_column_scoped_writes.py +0 -0
  238. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_committed_row_guard.py +0 -0
  239. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_composite_pk_filter.py +0 -0
  240. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_composite_pk_write_paths.py +0 -0
  241. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_config.py +0 -0
  242. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_conflict_target.py +0 -0
  243. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_conflict_writes.py +0 -0
  244. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_connection_codecs.py +0 -0
  245. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_connection_poison_guard.py +0 -0
  246. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_connection_primitives.py +0 -0
  247. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_count_composite_distinct.py +0 -0
  248. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_count_limit_param_leak.py +0 -0
  249. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_db_function_field.py +0 -0
  250. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_ddl_generator.py +0 -0
  251. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_deadlock_retry.py +0 -0
  252. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_derived_table.py +0 -0
  253. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_dirty_tracking.py +0 -0
  254. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_dynamic_crud.py +0 -0
  255. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_exceptions.py +0 -0
  256. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_execute_query_integrity_mapping.py +0 -0
  257. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_expression_primitives.py +0 -0
  258. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_fetch_on_init_holds_whole_table.py +0 -0
  259. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_fields.py +0 -0
  260. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_filter_jsonb_agg_array_index.py +0 -0
  261. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_fk_cycle_resolution.py +0 -0
  262. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_from_alias.py +0 -0
  263. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_fts.py +0 -0
  264. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_func_expression_args.py +0 -0
  265. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_graph_walk.py +0 -0
  266. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_having_annotate_alias.py +0 -0
  267. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_insert_ignore_and_admin_primitives.py +0 -0
  268. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_introspect_rls_policies.py +0 -0
  269. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_join_and_aggregate.py +0 -0
  270. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_latest_rows.py +0 -0
  271. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_listen_notify.py +0 -0
  272. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_local_sqlite.py +0 -0
  273. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_lock_contention_retry.py +0 -0
  274. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_managed_write_guard.py +0 -0
  275. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_matrx_entity.py +0 -0
  276. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_migration_diff_types.py +0 -0
  277. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_migration_loader.py +0 -0
  278. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_model_instance.py +0 -0
  279. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_model_meta.py +0 -0
  280. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_optimistic_concurrency.py +0 -0
  281. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_order_by_expression.py +0 -0
  282. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_org_assignment_inventory.py +0 -0
  283. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_orm_banners_are_client_safe.py +0 -0
  284. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_parameter_name_hydration.py +0 -0
  285. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_patch_jsonb_path.py +0 -0
  286. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_percentile_cont.py +0 -0
  287. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_persisted_count.py +0 -0
  288. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_person_scoped_session_bypasses_shared_cache.py +0 -0
  289. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_pool_create_never_wedges.py +0 -0
  290. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_pool_holder_census.py +0 -0
  291. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_pool_loop_recreation.py +0 -0
  292. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_pool_maintenance.py +0 -0
  293. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_postgrest_filters.py +0 -0
  294. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_pydantic_bridge.py +0 -0
  295. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_query_builder.py +0 -0
  296. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_query_error_detail.py +0 -0
  297. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_query_executor_sql.py +0 -0
  298. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_query_timeout.py +0 -0
  299. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_querybuilder_clone_semantics.py +0 -0
  300. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_queue_claim.py +0 -0
  301. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_read_write_declared.py +0 -0
  302. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_registry.py +0 -0
  303. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_registry_multi_database.py +0 -0
  304. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_registry_multi_schema.py +0 -0
  305. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_relational_backfill.py +0 -0
  306. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_relations.py +0 -0
  307. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_resilience.py +0 -0
  308. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_rls_session_scope.py +0 -0
  309. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_schema_exists.py +0 -0
  310. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_session_advisory_lock.py +0 -0
  311. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_standalone_write_session_context.py +0 -0
  312. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_state_cache.py +0 -0
  313. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_subquery_filter_raw.py +0 -0
  314. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_subquery_in_filter.py +0 -0
  315. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_subquery_nested_expressions.py +0 -0
  316. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_supabase_auth.py +0 -0
  317. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_supabase_config.py +0 -0
  318. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_sync_pool_declares_read_write.py +0 -0
  319. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_tls.py +0 -0
  320. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_transaction_database_identity.py +0 -0
  321. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_transaction_gucs_one_round_trip.py +0 -0
  322. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_transaction_pool_release_sends_nothing.py +0 -0
  323. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_typed_join_projection.py +0 -0
  324. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_typed_predicates.py +0 -0
  325. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_update_case_expression.py +0 -0
  326. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_update_subquery.py +0 -0
  327. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_update_with_rebase.py +0 -0
  328. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_upsert_default_leak.py +0 -0
  329. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_upsert_with_conflict.py +0 -0
  330. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_values_jsonb_decode.py +0 -0
  331. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_watched_lifecycle.py +0 -0
  332. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_write_queue_deadline.py +0 -0
  333. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level1/test_write_retry_semantics.py +0 -0
  334. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level2/MODULE_README.md +0 -0
  335. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level2/__init__.py +0 -0
  336. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level2/conftest.py +0 -0
  337. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level2/test_bulk_ops.py +0 -0
  338. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level2/test_cache_integration.py +0 -0
  339. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level2/test_crud.py +0 -0
  340. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level2/test_foreign_keys.py +0 -0
  341. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level2/test_m2m.py +0 -0
  342. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level2/test_manager.py +0 -0
  343. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level2/test_migrations_live.py +0 -0
  344. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level2/test_query_execution.py +0 -0
  345. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level2/test_schema_diff.py +0 -0
  346. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/level2/verify_committed_row_guard_pg17.py +0 -0
  347. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/live_pooler_role.py +0 -0
  348. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/native_source_cases.py +0 -0
  349. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/native_source_oracle.rs +0 -0
  350. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/sample_project/.env.example +0 -0
  351. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/sample_project/README.md +0 -0
  352. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/sample_project/__init__.py +0 -0
  353. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/sample_project/generate.py +0 -0
  354. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/sample_project/generated/.gitkeep +0 -0
  355. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/sample_project/matrx_orm.yaml +0 -0
  356. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/sample_project/test_schema_generation.py +0 -0
  357. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/sample_project_desktop/.env.example +0 -0
  358. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/sample_project_desktop/README.md +0 -0
  359. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/sample_project_desktop/__init__.py +0 -0
  360. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/sample_project_desktop/client_example.py +0 -0
  361. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/sample_project_desktop/client_example.ts +0 -0
  362. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/sample_project_desktop/client_supabase_example.py +0 -0
  363. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/sample_project_desktop/server.py +0 -0
  364. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/schema/entity_tests.py +0 -0
  365. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/schema/test_base_generation.py +0 -0
  366. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/schema/test_composite_pk_fk_generation.py +0 -0
  367. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/schema/test_composite_unique_generation.py +0 -0
  368. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/schema/test_dto_identity_generation.py +0 -0
  369. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/schema/test_entity_capability_generation.py +0 -0
  370. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/schema/test_enum_ordering.py +0 -0
  371. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/schema/test_excluded_fk_targets.py +0 -0
  372. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/schema/test_generate_schema.py +0 -0
  373. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/schema/test_junction_analysis.py +0 -0
  374. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/schema/test_multi_schema_output.py +0 -0
  375. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/schema/test_schema_analysis.py +0 -0
  376. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/schema/test_sql_expression_defaults.py +0 -0
  377. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/schema/test_table_relationships_query.py +0 -0
  378. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/schema/test_view_generation.py +0 -0
  379. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/schema_builder/test_array_default_parsing.py +0 -0
  380. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/schema_builder/test_enum_array_column.py +0 -0
  381. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/schema_builder/test_focused_generation_plan.py +0 -0
  382. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/schema_builder/test_host_models_aggregator.py +0 -0
  383. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/schema_builder/test_never_filter_columns.py +0 -0
  384. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/session/__init__.py +0 -0
  385. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/session/conftest.py +0 -0
  386. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/session/test_a_failed_capture_still_names_the_failure.py +0 -0
  387. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/session/test_a_grouped_update_writes_only_each_rows_own_fields.py +0 -0
  388. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/session/test_cache_no_downgrade.py +0 -0
  389. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/session/test_capture_poison_proof.py +0 -0
  390. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/session/test_coalesce.py +0 -0
  391. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/session/test_dag.py +0 -0
  392. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/session/test_disk_spill_fallback.py +0 -0
  393. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/session/test_flush_individual_fallback.py +0 -0
  394. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/session/test_governed_write.py +0 -0
  395. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/session/test_managed.py +0 -0
  396. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/session/test_op_enqueue_site.py +0 -0
  397. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/session/test_pending_insert_honors_operator_filters.py +0 -0
  398. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/session/test_reads.py +0 -0
  399. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/session/test_record_error_bounded_insert.py +0 -0
  400. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/session/test_record_error_storm.py +0 -0
  401. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/session/test_session.py +0 -0
  402. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/session/test_spill_guard_plugin.py +0 -0
  403. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/session/test_tests_never_write_live_system_error.py +0 -0
  404. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/test_a_backend_handed_over_mid_transaction_never_takes_our_writes.py +0 -0
  405. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/test_a_bulk_cache_never_stalls_the_loop.py +0 -0
  406. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/test_a_keyword_column_refuses_generation.py +0 -0
  407. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/test_a_list_of_uuids_binds_as_uuid_array.py +0 -0
  408. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/test_a_read_only_backend_never_refuses_our_writes.py +0 -0
  409. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/test_a_sealed_field_shows_no_character_of_its_value.py +0 -0
  410. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/test_array_arg_element_type.py +0 -0
  411. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/test_attachment_mutation_serialization.py +0 -0
  412. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/test_backup_alias_conflict.py +0 -0
  413. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/test_backup_history_restore.py +0 -0
  414. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/test_backup_restore_live.py +0 -0
  415. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/test_call_function_as.py +0 -0
  416. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/test_call_function_none_mode.py +0 -0
  417. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/test_composite_fk_is_not_a_column_fk.py +0 -0
  418. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/test_database_name_alias.py +0 -0
  419. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/test_db_functions.py +0 -0
  420. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/test_dsn_password_masking.py +0 -0
  421. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/test_dynamic_select_unregistered.py +0 -0
  422. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/test_first_asks_the_database_for_one_row.py +0 -0
  423. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/test_foreign_session_state.py +0 -0
  424. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/test_immutable_insert_or_exact_verify.py +0 -0
  425. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/test_keyed_digest.py +0 -0
  426. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/test_model_cls_refactor.py +0 -0
  427. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/test_native_enable.py +0 -0
  428. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/test_native_lifecycle.py +0 -0
  429. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/test_native_lifecycle_fences.py +0 -0
  430. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/test_native_passkey_receipts.py +0 -0
  431. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/test_native_source.py +0 -0
  432. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/test_no_blocking_asyncpg_tls.py +0 -0
  433. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/test_one_database.py +0 -0
  434. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/test_order_by_desc_nulls_last.py +0 -0
  435. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/test_org_vault_gate.py +0 -0
  436. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/test_package_wiring_views.py +0 -0
  437. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/test_pooler_role_guard.py +0 -0
  438. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/test_secret_audit_names_its_organization.py +0 -0
  439. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/test_secrets_battery.py +0 -0
  440. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/test_secrets_minting_keys.py +0 -0
  441. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/test_shared_lock_guard.py +0 -0
  442. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/test_sql_param_casts.py +0 -0
  443. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/test_state_cache_sweeps_stale.py +0 -0
  444. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/test_statement_ceiling_warning.py +0 -0
  445. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/test_sync_twins_guard.py +0 -0
  446. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/test_update_result_truthiness.py +0 -0
  447. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/test_user_visible_lookup.py +0 -0
  448. {matrx_orm-3.1.241 → matrx_orm-3.1.243}/tests/test_vault_aggregate_recovery_stateful.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: matrx-orm
3
- Version: 3.1.241
3
+ Version: 3.1.243
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
@@ -26,7 +26,7 @@ Requires-Dist: asyncpg>=0.31.0
26
26
  Requires-Dist: cbor2<7,>=6.1.3
27
27
  Requires-Dist: cryptography>=43.0
28
28
  Requires-Dist: gitpython>=3.1.40
29
- Requires-Dist: matrx-utils>=2.0.80
29
+ Requires-Dist: matrx-utils>=2.0.83
30
30
  Requires-Dist: psycopg-pool>=3.2.5
31
31
  Requires-Dist: psycopg[binary]>=3.2.5
32
32
  Requires-Dist: python-dotenv>=1.0
@@ -261,14 +261,21 @@ 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`.
264
+ **A read proves its own role; an empty result is never re-run** (2026-10-21). On a
265
+ transaction pool every single SELECT/WITH read is sent as `guarded_read_sql(query,
266
+ login)`: `SELECT * FROM (<query>) _matrx_guarded_read WHERE CASE WHEN current_user =
267
+ <login> AND session_user = <login> THEN true ELSE ('matrx_orm_borrowed_role:' ||
268
+ current_user)::int > 0 END`. The guard has no column references, so Postgres runs it
269
+ as a one-time filter before the scan (verified live with EXPLAIN; order and duplicate
270
+ column names preserved). A clean answer, empty or not, is proven by that one
271
+ statement; a borrowed role RAISES (22P02 naming the role), and only then does
272
+ `repair_guarded_read` emit the contamination alarm and re-read on a pinned, reset
273
+ session. `<login>` is learned once per pool by `learn_login_role` (pinned transaction,
274
+ both resets, `session_user`). Before this, every zero-row read was re-run as an
275
+ `EXISTS` proof: 3.5M extra statements in 5.7 h on the live database. A query that
276
+ cannot be wrapped (multi-statement, SHOW, VALUES) keeps the old path
277
+ (`prove_empty_in_one_statement`, then the pinned re-proof). Pinned by
278
+ `tests/level1/test_guarded_read_never_reruns_empty.py` and `tests/test_pooler_role_guard.py`.
272
279
 
273
280
  **Round trips are the launch cost on a remote pooler.** Every top-level BEGIN sets
274
281
  its ceilings, the host's declared session context and the opener's own settings
@@ -23,6 +23,10 @@ from matrx_orm.core.foreign_session_state import run_clearing_foreign_restrict
23
23
  from matrx_orm.core.pooler_role import (
24
24
  VERIFY_EMPTY_READS,
25
25
  assert_no_session_role_change,
26
+ borrowed_role_from_error,
27
+ guarded_read_sql,
28
+ learn_login_role,
29
+ repair_guarded_read,
26
30
  reprove_empty_read,
27
31
  )
28
32
  from matrx_orm.core.statement_ceiling import resolve_driver_timeout
@@ -454,6 +458,9 @@ class AsyncDatabaseManager:
454
458
  # Only a transaction-mode pool can be handed a server connection another
455
459
  # client left wearing a role, so a direct Postgres pays nothing.
456
460
  _empty_read_verification: dict[str, bool] = {}
461
+ # Our own login role per transaction-pool config, learned ONCE on a pinned,
462
+ # reset session; every guarded read compares the backend's identity to it.
463
+ _login_roles: dict[str, str] = {}
457
464
 
458
465
  @classmethod
459
466
  def _verifies_empty_reads(cls, config_name: str) -> bool:
@@ -1464,11 +1471,56 @@ class AsyncDatabaseManager:
1464
1471
  in_transaction=False,
1465
1472
  )
1466
1473
  try:
1474
+ # Layer 2: on a transaction pool the read carries its own
1475
+ # role proof (pooler_role.guarded_read_sql). A clean result —
1476
+ # empty or not — is proven by this one statement; the guard
1477
+ # RAISES on a borrowed role, and only then is the read repaired.
1478
+ guarded_sql: str | None = None
1479
+ login_role: str | None = None
1480
+ if (
1481
+ verify_empty
1482
+ and read_only
1483
+ and cls._verifies_empty_reads(config_name)
1484
+ ):
1485
+ login_role = cls._login_roles.get(config_name)
1486
+ if login_role is None:
1487
+ login_role = await learn_login_role(conn)
1488
+ cls._login_roles[config_name] = login_role
1489
+ guarded_sql = guarded_read_sql(query, login_role)
1490
+ repaired: list[list[dict[str, Any]]] = []
1491
+
1492
+ async def _fetch() -> list[Any]:
1493
+ if guarded_sql is None:
1494
+ return await conn.fetch(query, *args, timeout=driver_timeout)
1495
+ try:
1496
+ return await conn.fetch(
1497
+ guarded_sql, *args, timeout=driver_timeout
1498
+ )
1499
+ except asyncpg.exceptions.InvalidTextRepresentationError as exc:
1500
+ borrowed = borrowed_role_from_error(exc)
1501
+ if borrowed is None:
1502
+ raise
1503
+ repaired.append(
1504
+ await repair_guarded_read(
1505
+ conn,
1506
+ config_name=config_name,
1507
+ query=query,
1508
+ args=args,
1509
+ statement_timeout=driver_timeout,
1510
+ normalize=_normalize_record,
1511
+ borrowed_role=borrowed,
1512
+ login_role=login_role or "<unknown>",
1513
+ )
1514
+ )
1515
+ return []
1516
+
1467
1517
  results = await run_clearing_foreign_restrict(
1468
1518
  conn,
1469
- lambda: conn.fetch(query, *args, timeout=driver_timeout),
1519
+ _fetch,
1470
1520
  what=f"execute_query {query[:80]!r}",
1471
1521
  )
1522
+ if repaired:
1523
+ return repaired[0]
1472
1524
  if read_attempt > 0:
1473
1525
  logger.warning(
1474
1526
  "[read-retry] SELECT HEALED on attempt %d after a "
@@ -1478,14 +1530,16 @@ class AsyncDatabaseManager:
1478
1530
  )
1479
1531
  if (
1480
1532
  not results
1533
+ and guarded_sql is None
1481
1534
  and verify_empty
1482
1535
  and read_only
1483
1536
  and cls._verifies_empty_reads(config_name)
1484
1537
  ):
1485
- # Layer 2: an empty result from a pooled connection is
1486
- # unproven until a session that can state its own role
1487
- # says so. A borrowed `authenticated` role returns ZERO
1488
- # ROWS on an RLS table instead of raising.
1538
+ # A read that could not carry its own guard (not one
1539
+ # SELECT/WITH statement): an empty result from a pooled
1540
+ # connection is unproven until a session that can state
1541
+ # its own role says so. A borrowed `authenticated` role
1542
+ # returns ZERO ROWS on an RLS table instead of raising.
1489
1543
  true_rows = await reprove_empty_read(
1490
1544
  conn,
1491
1545
  config_name=config_name,
@@ -35,9 +35,17 @@ TWO INDEPENDENT LAYERS, each sufficient alone, each screaming when it fires:
35
35
  this guards everything else. The sanctioned ways to change role remain
36
36
  ``matrx_orm.rls_session`` and ``db/pooler_session.py::impersonate``.
37
37
 
38
- 2. **Reconciliation — an empty read is re-proved on a session that can prove its
39
- role.** A zero-row result from a read-only query on a transaction-pool config
40
- is re-run inside an explicit transaction (pinned to ONE server connection).
38
+ 2. **Proof in the read itself, repair only when it fires.** On a transaction-pool
39
+ config every read-only query is sent wrapped by ``guarded_read_sql``: the
40
+ statement carries a one-time filter that RAISES when the backend's
41
+ ``current_user``/``session_user`` is not our login role (learned once per pool
42
+ by ``learn_login_role``). One statement runs on one server connection, so a
43
+ clean answer — rows or no rows — is proven by the same round trip that read
44
+ it; nothing is ever re-run because a result was empty (2026-10-21: the old
45
+ per-empty-result re-proof was 3.5M extra statements in 5.7 h). Only when the
46
+ guard fires, or a query cannot be wrapped (multi-statement), does the
47
+ repairing re-read below run: re-run inside an explicit transaction (pinned to
48
+ ONE server connection).
41
49
  The pinned session's identity is captured before and after resetting session
42
50
  authorization and role. The caller gets any rows that appeared, but a
43
51
  contamination alarm is emitted only when the role change was directly
@@ -46,9 +54,9 @@ TWO INDEPENDENT LAYERS, each sufficient alone, each screaming when it fires:
46
54
 
47
55
  Layer 2 follows the repo's guard doctrine: a guard that CAN reconcile MUST
48
56
  reconcile — we hold the correct answer, so killing the caller's request would be
49
- strictly worse than returning it loudly. It costs nothing on a non-empty read and
50
- nothing at all on a direct (session-mode) Postgres connection, where cross-client
51
- contamination cannot happen.
57
+ strictly worse than returning it loudly. A clean read pays one constant
58
+ one-time filter; a direct (session-mode) Postgres connection pays nothing at all,
59
+ because cross-client contamination cannot happen there.
52
60
  """
53
61
 
54
62
  from __future__ import annotations
@@ -223,6 +231,115 @@ async def emit_contamination(event: EmptyReadContaminationEvent) -> None:
223
231
  logger.exception("[pooler-role] contamination sink raised")
224
232
 
225
233
 
234
+ # The text the guard's one-time filter raises with. ``('<sentinel>' || role)::int``
235
+ # fails with 22P02 and names the borrowed role; nothing else in a read produces it.
236
+ BORROWED_ROLE_SENTINEL = "matrx_orm_borrowed_role:"
237
+ _SENTINEL_IN_ERROR = re.compile(re.escape(BORROWED_ROLE_SENTINEL) + r"([^\"']*)")
238
+
239
+
240
+ def _sql_literal(value: str) -> str:
241
+ return "'" + value.replace("'", "''") + "'"
242
+
243
+
244
+ def guarded_read_sql(query: str, login_role: str | None) -> str | None:
245
+ """``query`` wrapped so the read proves its own role, or ``None`` if it cannot be.
246
+
247
+ ``SELECT * FROM (<query>) _q WHERE <guard>``: the guard has no column
248
+ references, so Postgres plans it as a ONE-TIME FILTER evaluated once before
249
+ the scan (measured live 2026-10-21 — ``Result / One-Time Filter`` over the
250
+ original plan, order and duplicate column names preserved). On our login
251
+ role it is ``true``; on any other ``current_user`` or ``session_user`` it
252
+ casts ``BORROWED_ROLE_SENTINEL || current_user`` to int and RAISES, so a
253
+ borrowed role can never come back as an empty or partial result. Comparing
254
+ both identities against the login role learned for this pool also catches a
255
+ ``SET SESSION AUTHORIZATION`` swap, where the two agree on the wrong name.
256
+
257
+ Returns ``None`` (caller falls back to the repairing re-proof on empty) for
258
+ a query that is not a single SELECT/WITH statement or when the login role is
259
+ not yet known.
260
+ """
261
+ if not login_role:
262
+ return None
263
+ body = query.strip().rstrip(";").strip()
264
+ if not body or ";" in body:
265
+ return None
266
+ head = body.lstrip("( \n\t")[:6].lower()
267
+ if not (head.startswith("select") or head.startswith("with")):
268
+ # A leading comment, VALUES, TABLE, SHOW, EXPLAIN: not provably embeddable.
269
+ return None
270
+ role = _sql_literal(login_role)
271
+ # Newlines around the body: a trailing ``-- comment`` must not swallow the
272
+ # closing parenthesis.
273
+ return (
274
+ "SELECT * FROM (\n"
275
+ f"{body}\n"
276
+ ") _matrx_guarded_read WHERE CASE WHEN current_user = "
277
+ f"{role} AND session_user = {role} THEN true "
278
+ f"ELSE ({_sql_literal(BORROWED_ROLE_SENTINEL)} || current_user)::int > 0 END"
279
+ )
280
+
281
+
282
+ def borrowed_role_from_error(exc: BaseException) -> str | None:
283
+ """The role named by a fired read guard, or ``None`` for any other error."""
284
+ match = _SENTINEL_IN_ERROR.search(str(exc))
285
+ if match is None:
286
+ return None
287
+ return match.group(1) or "<unknown>"
288
+
289
+
290
+ async def learn_login_role(conn: Any) -> str:
291
+ """Our own login role, read on a pinned, reset session (once per pool).
292
+
293
+ The explicit transaction pins every statement to one server connection, and
294
+ both resets put a borrowed role back before ``session_user`` is read, so the
295
+ answer is the role the backend authenticated as — never a borrowed one.
296
+ """
297
+ async with conn.transaction():
298
+ await conn.execute("RESET SESSION AUTHORIZATION")
299
+ await conn.execute("RESET ROLE")
300
+ row = await conn.fetchrow("SELECT session_user AS login_, current_user AS current_")
301
+ login = row["login_"]
302
+ if not login or row["current_"] != login:
303
+ raise PoolerRoleContamination(
304
+ f"could not learn the login role: a reset session still runs as "
305
+ f"'{row['current_']}' (session user '{login}')"
306
+ )
307
+ return str(login)
308
+
309
+
310
+ async def repair_guarded_read(
311
+ conn: Any,
312
+ *,
313
+ config_name: str,
314
+ query: str,
315
+ args: tuple[Any, ...],
316
+ statement_timeout: float | None,
317
+ normalize: Callable[[Any], dict[str, Any]],
318
+ borrowed_role: str,
319
+ login_role: str,
320
+ ) -> list[dict[str, Any]]:
321
+ """The read guard fired: scream, then re-read on a pinned, repaired session.
322
+
323
+ The guard's raise IS direct observation of a borrowed role on the backend
324
+ that served the read, so the contamination alarm is emitted unconditionally.
325
+ The caller gets the true rows (``[]`` when the clean read is empty).
326
+ """
327
+ rows, _observed = await _pinned_reread(
328
+ conn, query=query, args=args, statement_timeout=statement_timeout, normalize=normalize
329
+ )
330
+ await emit_contamination(
331
+ EmptyReadContaminationEvent(
332
+ config_name=config_name,
333
+ query=query,
334
+ contaminated_role=borrowed_role,
335
+ login_role=login_role,
336
+ true_row_count=len(rows or []),
337
+ occurred_at=datetime.now(UTC),
338
+ )
339
+ )
340
+ return rows or []
341
+
342
+
226
343
  def _single_statement_proof_sql(query: str) -> str | None:
227
344
  """The one-statement absence proof for ``query``, or ``None`` if it cannot embed.
228
345
 
@@ -306,6 +423,44 @@ async def reprove_empty_read(
306
423
  ):
307
424
  return None
308
425
 
426
+ rows, observed = await _pinned_reread(
427
+ conn, query=query, args=args, statement_timeout=statement_timeout, normalize=normalize
428
+ )
429
+ if rows is None:
430
+ return None
431
+ # Rows may legitimately appear between two READ COMMITTED statements.
432
+ # guest_executions is the canonical example: concurrent first requests
433
+ # for one fingerprint race a lookup against creation. Empty -> non-empty
434
+ # is reconciliation, not proof of pool contamination. Only the captured
435
+ # before/after identity change can put this class in the repair queue.
436
+ if observed is not None:
437
+ await emit_contamination(
438
+ EmptyReadContaminationEvent(
439
+ config_name=config_name,
440
+ query=query,
441
+ contaminated_role=observed[0],
442
+ login_role=observed[1],
443
+ true_row_count=len(rows),
444
+ occurred_at=datetime.now(UTC),
445
+ )
446
+ )
447
+ return rows
448
+
449
+
450
+ async def _pinned_reread(
451
+ conn: Any,
452
+ *,
453
+ query: str,
454
+ args: tuple[Any, ...],
455
+ statement_timeout: float | None,
456
+ normalize: Callable[[Any], dict[str, Any]],
457
+ ) -> tuple[list[dict[str, Any]] | None, tuple[str, str] | None]:
458
+ """Re-read on ONE server connection after resetting both identities.
459
+
460
+ Returns ``(rows or None when empty, (borrowed, login) when the reset was seen
461
+ to change the identity else None)``. Raises ``PoolerRoleContamination`` when
462
+ no attempt lands on a clean session.
463
+ """
309
464
  last_role: tuple[str, str] | None = None
310
465
  for _ in range(REPROVE_ATTEMPTS):
311
466
  async with conn.transaction():
@@ -325,29 +480,14 @@ async def reprove_empty_read(
325
480
  last_role = (current, login)
326
481
  continue
327
482
  rows = await conn.fetch(query, *args, timeout=statement_timeout)
328
- if not rows:
329
- return None
330
- true_rows = [normalize(r) for r in rows]
331
-
332
- observed_contamination = (before_current, before_session) != (current, login)
333
-
334
- # Rows may legitimately appear between two READ COMMITTED statements.
335
- # guest_executions is the canonical example: concurrent first requests
336
- # for one fingerprint race a lookup against creation. Empty -> non-empty
337
- # is reconciliation, not proof of pool contamination. Only the captured
338
- # before/after identity change can put this class in the repair queue.
339
- if observed_contamination:
340
- await emit_contamination(
341
- EmptyReadContaminationEvent(
342
- config_name=config_name,
343
- query=query,
344
- contaminated_role=before_current,
345
- login_role=login,
346
- true_row_count=len(true_rows),
347
- occurred_at=datetime.now(UTC),
348
- )
483
+ observed = (
484
+ (before_current, login)
485
+ if (before_current, before_session) != (current, login)
486
+ else None
349
487
  )
350
- return true_rows
488
+ if not rows:
489
+ return None, observed
490
+ return [normalize(r) for r in rows], observed
351
491
 
352
492
  current, login = last_role if last_role is not None else ("<unknown>", "<unknown>")
353
493
  raise PoolerRoleContamination(
@@ -358,6 +498,7 @@ async def reprove_empty_read(
358
498
 
359
499
 
360
500
  __all__ = [
501
+ "BORROWED_ROLE_SENTINEL",
361
502
  "ContaminationSink",
362
503
  "EmptyReadContaminationEvent",
363
504
  "PoolerRoleContamination",
@@ -365,8 +506,12 @@ __all__ = [
365
506
  "SessionRoleChangeRefused",
366
507
  "VERIFY_EMPTY_READS",
367
508
  "assert_no_session_role_change",
509
+ "borrowed_role_from_error",
368
510
  "emit_contamination",
511
+ "guarded_read_sql",
512
+ "learn_login_role",
369
513
  "register_contamination_sink",
514
+ "repair_guarded_read",
370
515
  "reprove_empty_read",
371
516
  "reset_contamination_sinks",
372
517
  ]
@@ -1722,7 +1722,7 @@ class QueryExecutor:
1722
1722
  # it here and re-raising the BASE class downgraded every one of them to a
1723
1723
  # generic DatabaseError, so a caller could no longer tell "permission
1724
1724
  # denied" from "a child row still references this" — and `str(e)` folded
1725
- # the original's ANSI-coloured multi-line block into a message. Found by
1725
+ # the original's ANSI-colored multi-line block into a message. Found by
1726
1726
  # `scripts/_verify_batch_access_model.py`, whose whole job is asserting a
1727
1727
  # user's DELETE is refused for the RIGHT reason: `except
1728
1728
  # PermissionDeniedError` silently missed it and the probe reported the
@@ -742,11 +742,15 @@ async def record_error(
742
742
  admitted, folded = _storm_admit(kind, resolved_error_type, route)
743
743
  if not admitted:
744
744
  # Counted, never silent: the next admitted row for this signature
745
- # carries the total; the stdout line keeps each one greppable.
745
+ # carries the total. ONE stdout line per window (the first fold) — a line per
746
+ # folded row re-created the storm in ops.app_log (1,044 lines in 6 h, DB lane C
747
+ # 2026-10-21), which is the volume this budget exists to stop.
748
+ if folded != 1:
749
+ return
746
750
  vcprint(
747
751
  f"[matrx_orm.session] storm: {kind}/{resolved_error_type} on {route or '-'} "
748
- f"({folded} suppressed this {STORM_WINDOW_SECONDS:.0f}s window after "
749
- f"{STORM_ROW_BUDGET} rows) — folded into the next captured row, not filed",
752
+ f"(budget of {STORM_ROW_BUDGET} rows spent; further captures this "
753
+ f"{STORM_WINDOW_SECONDS:.0f}s window are counted and folded into the next captured row, not filed)",
750
754
  color="yellow",
751
755
  )
752
756
  return
@@ -416,7 +416,8 @@ def _should_emit_stuck_rows_alert(
416
416
 
417
417
  def _build_select_query(cfg: WatchedLifecycleConfig, limit: int) -> tuple[str, list[Any]]:
418
418
  params: list[Any] = []
419
- predicate_sql = cfg.predicate.as_sql(params)
419
+ # The watched table is the predicate's outer table, so an Exists(... OuterRef(...)) can name it.
420
+ predicate_sql = cfg.predicate.as_sql(params, current_table=f"{cfg.schema}.{cfg.table}")
420
421
  params.append(cfg.max_age_secs)
421
422
  diagnostics = "".join(
422
423
  f", {col} AS diagnostic_{index}"
@@ -524,7 +525,8 @@ async def _transition_stuck_row(
524
525
  if not set_clauses:
525
526
  return False
526
527
 
527
- predicate_sql = cfg.predicate.as_sql(params)
528
+ # The watched table is the predicate's outer table, so an Exists(... OuterRef(...)) can name it.
529
+ predicate_sql = cfg.predicate.as_sql(params, current_table=f"{cfg.schema}.{cfg.table}")
528
530
  params.append(pk_value)
529
531
  pk_param = len(params)
530
532
  params.append(cfg.max_age_secs)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "matrx-orm"
3
- version = "3.1.241"
3
+ version = "3.1.243"
4
4
  description = "Async-first PostgreSQL ORM with bidirectional migrations, schema introspection, many-to-many relationships, and built-in state caching"
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
@@ -41,7 +41,7 @@ dependencies = [
41
41
  "gitpython>=3.1.40",
42
42
  "psycopg[binary]>=3.2.5",
43
43
  "psycopg-pool>=3.2.5",
44
- "matrx-utils>=2.0.80",
44
+ "matrx-utils>=2.0.83",
45
45
  "pyyaml>=6.0.3",
46
46
  "python-dotenv>=1.0",
47
47
  "cryptography>=43.0",
@@ -0,0 +1,142 @@
1
+ """Forcing function: an empty read is proven by the read itself, never re-run.
2
+
3
+ 2026-10-21 census: re-proving every zero-row read was 3.5M extra statements in
4
+ 5.7 h on the live database (the operation-stream reaper's empty lookups alone
5
+ were 2.48M). The fix moves the role proof INTO the read (``guarded_read_sql``),
6
+ so these tests fail if any path goes back to a second statement per empty
7
+ result — and fail just as hard if a borrowed role could slip through silently.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from contextlib import asynccontextmanager
13
+ from typing import Any
14
+
15
+ import asyncpg
16
+ import pytest
17
+
18
+ from matrx_orm.core.async_db_manager import AsyncDatabaseManager
19
+ from matrx_orm.core.pooler_role import (
20
+ BORROWED_ROLE_SENTINEL,
21
+ EmptyReadContaminationEvent,
22
+ guarded_read_sql,
23
+ register_contamination_sink,
24
+ reset_contamination_sinks,
25
+ )
26
+
27
+ _GUARDED_PREFIX = "SELECT * FROM (\n"
28
+
29
+
30
+ class _Tx:
31
+ def __init__(self, conn: _Conn) -> None:
32
+ self._conn = conn
33
+
34
+ async def __aenter__(self) -> None:
35
+ self._conn.log.append("BEGIN")
36
+
37
+ async def __aexit__(self, *_exc: object) -> bool:
38
+ self._conn.log.append("COMMIT")
39
+ return False
40
+
41
+
42
+ class _Conn:
43
+ """A pooled connection whose backend may be wearing a borrowed role."""
44
+
45
+ def __init__(self, *, rows: list[dict[str, Any]], borrowed: str | None = None) -> None:
46
+ self.rows = rows
47
+ self.borrowed = borrowed
48
+ self.log: list[str] = []
49
+
50
+ def transaction(self) -> _Tx:
51
+ return _Tx(self)
52
+
53
+ async def execute(self, sql: str, *_a: object, **_k: object) -> None:
54
+ self.log.append(sql)
55
+
56
+ async def fetchrow(self, sql: str, *_a: object, **_k: object) -> dict[str, Any]:
57
+ self.log.append(sql)
58
+ if "login_" in sql:
59
+ return {"login_": "postgres", "current_": "postgres"}
60
+ return {"current_user": "postgres", "session_user": "postgres"}
61
+
62
+ async def fetch(self, sql: str, *_a: object, **_k: object) -> list[dict[str, Any]]:
63
+ self.log.append(sql)
64
+ if sql.startswith(_GUARDED_PREFIX):
65
+ if self.borrowed is not None:
66
+ # What Postgres raises when the guard's one-time filter fires.
67
+ raise asyncpg.exceptions.InvalidTextRepresentationError(
68
+ f'invalid input syntax for type integer: "{BORROWED_ROLE_SENTINEL}{self.borrowed}"'
69
+ )
70
+ return list(self.rows)
71
+ # The unguarded text only runs inside the pinned repair transaction.
72
+ return list(self.rows)
73
+
74
+
75
+ def _install(monkeypatch: pytest.MonkeyPatch, conn: _Conn) -> None:
76
+ @asynccontextmanager
77
+ async def checkout(_cls: object, _config_name: str, _timeout: object):
78
+ yield conn
79
+
80
+ monkeypatch.setattr(AsyncDatabaseManager, "get_connection", classmethod(checkout))
81
+ monkeypatch.setattr(
82
+ AsyncDatabaseManager, "_verifies_empty_reads", classmethod(lambda _c, _n: True)
83
+ )
84
+ monkeypatch.setattr(AsyncDatabaseManager, "_login_roles", {})
85
+
86
+
87
+ _QUERY = "SELECT id FROM runtime.global_execution WHERE request_id = $1"
88
+
89
+
90
+ @pytest.mark.asyncio
91
+ async def test_empty_read_on_transaction_pool_is_one_guarded_statement(monkeypatch) -> None:
92
+ conn = _Conn(rows=[])
93
+ _install(monkeypatch, conn)
94
+
95
+ for _ in range(5):
96
+ rows = await AsyncDatabaseManager.execute_query("test", _QUERY, "00000000")
97
+ assert rows == []
98
+
99
+ reads = [s for s in conn.log if s.startswith("SELECT") and "runtime.global_execution" in s]
100
+ # Five empty reads = five statements, each carrying its own role proof.
101
+ assert len(reads) == 5, conn.log
102
+ assert all(s.startswith(_GUARDED_PREFIX) for s in reads), reads
103
+ # The login role is learned once per pool, not once per read.
104
+ assert conn.log.count("RESET ROLE") == 1, conn.log
105
+ assert not any("EXISTS (" in s for s in conn.log), "empty read was re-proved"
106
+
107
+
108
+ @pytest.mark.asyncio
109
+ async def test_a_borrowed_role_never_comes_back_empty(monkeypatch) -> None:
110
+ reset_contamination_sinks()
111
+ events: list[EmptyReadContaminationEvent] = []
112
+ register_contamination_sink(events.append)
113
+ monkeypatch.setattr("matrx_orm.session.fallback.record_error", _noop_record, raising=False)
114
+
115
+ conn = _Conn(rows=[{"id": "x1"}], borrowed="authenticated")
116
+ _install(monkeypatch, conn)
117
+
118
+ rows = await AsyncDatabaseManager.execute_query("test", _QUERY, "00000000")
119
+
120
+ assert rows == [{"id": "x1"}]
121
+ assert len(events) == 1
122
+ assert events[0].contaminated_role == "authenticated"
123
+ assert events[0].login_role == "postgres"
124
+ reset_contamination_sinks()
125
+
126
+
127
+ async def _noop_record(*_a: object, **_k: object) -> None:
128
+ return None
129
+
130
+
131
+ def test_guard_wraps_single_reads_and_refuses_the_rest() -> None:
132
+ wrapped = guarded_read_sql("SELECT 1 -- trailing comment", "postgres")
133
+ assert wrapped is not None
134
+ assert wrapped.startswith(_GUARDED_PREFIX)
135
+ # A trailing line comment must not swallow the closing parenthesis.
136
+ assert "-- trailing comment\n)" in wrapped
137
+ assert "current_user = 'postgres' AND session_user = 'postgres'" in wrapped
138
+ assert guarded_read_sql("WITH t AS (SELECT 1) SELECT * FROM t;", "postgres") is not None
139
+ assert guarded_read_sql("SELECT 1; SELECT 2", "postgres") is None
140
+ assert guarded_read_sql("SHOW search_path", "postgres") is None
141
+ assert guarded_read_sql("SELECT 1", None) is None
142
+ assert "'o''brien'" in (guarded_read_sql("SELECT 1", "o'brien") or "")
@@ -56,9 +56,16 @@ async def test_read_retries_disconnect_masked_by_reproof_transaction(monkeypatch
56
56
  )
57
57
 
58
58
  class ClosedConnection:
59
+ # The pinned transaction is now the once-per-pool login-role read
60
+ # (pooler_role.learn_login_role) rather than a per-empty-read re-proof.
59
61
  async def fetch(self, *_args, **_kwargs):
60
62
  return []
61
63
 
64
+ async def execute(self, *_args, **_kwargs):
65
+ raise asyncpg.exceptions.ConnectionDoesNotExistError(
66
+ "connection was closed in the middle of operation"
67
+ )
68
+
62
69
  async def fetchrow(self, *_args, **_kwargs):
63
70
  raise asyncpg.exceptions.ConnectionDoesNotExistError(
64
71
  "connection was closed in the middle of operation"
@@ -78,6 +85,22 @@ async def test_read_retries_disconnect_masked_by_reproof_transaction(monkeypatch
78
85
  async def fetch(self, *_args, **_kwargs):
79
86
  return [{"value": 1}]
80
87
 
88
+ async def execute(self, *_args, **_kwargs):
89
+ return None
90
+
91
+ async def fetchrow(self, *_args, **_kwargs):
92
+ return {"login_": "postgres", "current_": "postgres"}
93
+
94
+ def transaction(self):
95
+ class _Tx:
96
+ async def __aenter__(self):
97
+ return self
98
+
99
+ async def __aexit__(self, *_a):
100
+ return False
101
+
102
+ return _Tx()
103
+
81
104
  def is_closed(self) -> bool:
82
105
  return False
83
106
 
@@ -89,6 +112,7 @@ async def test_read_retries_disconnect_masked_by_reproof_transaction(monkeypatch
89
112
 
90
113
  monkeypatch.setattr(AsyncDatabaseManager, "get_connection", classmethod(checkout))
91
114
  monkeypatch.setattr(AsyncDatabaseManager, "_verifies_empty_reads", lambda *_: True)
115
+ monkeypatch.setattr(AsyncDatabaseManager, "_login_roles", {})
92
116
  monkeypatch.setattr(
93
117
  "matrx_orm.core.async_db_manager.READ_TIMEOUT_RETRY_BACKOFF", 0
94
118
  )
File without changes
File without changes