matrx-orm 3.1.75__tar.gz → 3.1.76__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 (339) hide show
  1. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/PKG-INFO +2 -2
  2. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/FEATURE.md +25 -2
  3. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/pooler_role.py +44 -27
  4. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/pyproject.toml +2 -2
  5. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/live_pooler_role.py +28 -1
  6. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/test_pooler_role_guard.py +86 -4
  7. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/.gitignore +0 -0
  8. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/AGENTS.md +0 -0
  9. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/BASE_CLASS_METHODS.md +0 -0
  10. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/CLAUDE.md +0 -0
  11. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/MODEL_API.md +0 -0
  12. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/README.md +0 -0
  13. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/database/orm/extended/managers/ai_model_base.py +0 -0
  14. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/docs/migrations.md +0 -0
  15. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/MODULE_README.md +0 -0
  16. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/__init__.py +0 -0
  17. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/adapters/MODULE_README.md +0 -0
  18. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/adapters/__init__.py +0 -0
  19. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/adapters/async_postgresql.py +0 -0
  20. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/adapters/base_adapter.py +0 -0
  21. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/adapters/postgresql.py +0 -0
  22. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/adapters/postgrest_client_adapter.py +0 -0
  23. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/adapters/supabase_adapter.py +0 -0
  24. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/admin/__init__.py +0 -0
  25. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/admin/router.py +0 -0
  26. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/api/__init__.py +0 -0
  27. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/api/auth.py +0 -0
  28. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/api/config.py +0 -0
  29. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/api/handlers.py +0 -0
  30. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/api/protocol.py +0 -0
  31. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/api/server.py +0 -0
  32. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/associations.py +0 -0
  33. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/cache_debug.py +0 -0
  34. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/catalog.py +0 -0
  35. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/catalog_sql.py +0 -0
  36. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/client/MODULE_README.md +0 -0
  37. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/client/__init__.py +0 -0
  38. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/client/postgres_connection.py +0 -0
  39. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/client/postgrest.py +0 -0
  40. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/client/supabase_auth.py +0 -0
  41. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/client/supabase_config.py +0 -0
  42. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/client/supabase_manager.py +0 -0
  43. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/EXTENDED-TASK.md +0 -0
  44. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/MODULE_README.md +0 -0
  45. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/RELATIONS-TASKS.md +0 -0
  46. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/TASKS.md +0 -0
  47. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/__init__.py +0 -0
  48. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/async_db_manager.py +0 -0
  49. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/base.py +0 -0
  50. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/config.py +0 -0
  51. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/connections.py +0 -0
  52. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/db_function.py +0 -0
  53. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/diagnostics.py +0 -0
  54. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/expressions.py +0 -0
  55. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/extended.py +0 -0
  56. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/fields.py +0 -0
  57. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/introspection.py +0 -0
  58. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/listen.py +0 -0
  59. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/loop_filters.py +0 -0
  60. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/model_dto.py +0 -0
  61. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/model_view.py +0 -0
  62. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/one_database.py +0 -0
  63. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/paginator.py +0 -0
  64. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/pool_maintenance.py +0 -0
  65. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/pool_watch.py +0 -0
  66. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/pydantic_bridge.py +0 -0
  67. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/registry.py +0 -0
  68. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/relations.py +0 -0
  69. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/resilience.py +0 -0
  70. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/rls_session.py +0 -0
  71. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/signals.py +0 -0
  72. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/tls.py +0 -0
  73. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/transaction.py +0 -0
  74. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/types.py +0 -0
  75. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/write_queue.py +0 -0
  76. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/entity.py +0 -0
  77. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/error_handling.py +0 -0
  78. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/exceptions.py +0 -0
  79. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/extended/__init__.py +0 -0
  80. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/extended/app_error_handler.py +0 -0
  81. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/identity_rule.py +0 -0
  82. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/middleware/__init__.py +0 -0
  83. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/middleware/base.py +0 -0
  84. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/migrations/MODULE_README.md +0 -0
  85. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/migrations/__init__.py +0 -0
  86. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/migrations/cli.py +0 -0
  87. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/migrations/ddl.py +0 -0
  88. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/migrations/diff.py +0 -0
  89. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/migrations/executor.py +0 -0
  90. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/migrations/integration.py +0 -0
  91. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/migrations/loader.py +0 -0
  92. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/migrations/operations.py +0 -0
  93. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/migrations/state.py +0 -0
  94. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/migrations/table_filter.py +0 -0
  95. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/operations/MODULE_README.md +0 -0
  96. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/operations/__init__.py +0 -0
  97. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/operations/bulk_update_values.py +0 -0
  98. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/operations/conflict.py +0 -0
  99. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/operations/conflict_writes.py +0 -0
  100. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/operations/create.py +0 -0
  101. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/operations/db_functions.py +0 -0
  102. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/operations/delete.py +0 -0
  103. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/operations/dynamic_admin.py +0 -0
  104. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/operations/dynamic_crud.py +0 -0
  105. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/operations/graph_walk.py +0 -0
  106. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/operations/latest_rows.py +0 -0
  107. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/operations/queue_claim.py +0 -0
  108. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/operations/read.py +0 -0
  109. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/operations/relational_backfill.py +0 -0
  110. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/operations/staging_load.py +0 -0
  111. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/operations/update.py +0 -0
  112. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/platform_db.py +0 -0
  113. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/py.typed +0 -0
  114. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/python_sql/MODULE_README.md +0 -0
  115. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/python_sql/__init__.py +0 -0
  116. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/python_sql/db_objects.py +0 -0
  117. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/python_sql/table_detailed_relationships.py +0 -0
  118. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/python_sql/table_typescript_relationship.py +0 -0
  119. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/query/FEATURE.md +0 -0
  120. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/query/MODULE_README.md +0 -0
  121. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/query/__init__.py +0 -0
  122. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/query/builder.py +0 -0
  123. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/query/executor.py +0 -0
  124. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/schema_builder/FEATURE.md +0 -0
  125. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/schema_builder/MODULE_README.md +0 -0
  126. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/schema_builder/__init__.py +0 -0
  127. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/schema_builder/code_handler.py +0 -0
  128. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/schema_builder/codegen_writer.py +0 -0
  129. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/schema_builder/columns.py +0 -0
  130. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/schema_builder/common.py +0 -0
  131. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/schema_builder/diff_preview.py +0 -0
  132. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/schema_builder/entity_capabilities.py +0 -0
  133. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/schema_builder/generator.py +0 -0
  134. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/schema_builder/helpers/__init__.py +0 -0
  135. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/schema_builder/helpers/base_generators.py +0 -0
  136. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/schema_builder/helpers/entity_generators.py +0 -0
  137. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/schema_builder/helpers/git_checker.py +0 -0
  138. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/schema_builder/package_wiring.py +0 -0
  139. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/schema_builder/relationships.py +0 -0
  140. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/schema_builder/runner.py +0 -0
  141. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/schema_builder/schema.py +0 -0
  142. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/schema_builder/schema_manager.py +0 -0
  143. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/schema_builder/tables.py +0 -0
  144. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/schema_builder/views.py +0 -0
  145. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/secrets_battery/FEATURE.md +0 -0
  146. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/secrets_battery/__init__.py +0 -0
  147. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/secrets_battery/attachments.py +0 -0
  148. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/secrets_battery/crypto.py +0 -0
  149. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/secrets_battery/items.py +0 -0
  150. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/secrets_battery/model.py +0 -0
  151. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/secrets_battery/org_service.py +0 -0
  152. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/secrets_battery/service.py +0 -0
  153. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/session/README.md +0 -0
  154. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/session/__init__.py +0 -0
  155. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/session/coalesce.py +0 -0
  156. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/session/dag.py +0 -0
  157. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/session/errors.py +0 -0
  158. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/session/fallback.py +0 -0
  159. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/session/flush.py +0 -0
  160. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/session/lifecycle.py +0 -0
  161. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/session/managed.py +0 -0
  162. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/session/op.py +0 -0
  163. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/session/reads.py +0 -0
  164. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/session/session.py +0 -0
  165. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/session/telemetry.py +0 -0
  166. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/sql_executor/MODULE_README.md +0 -0
  167. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/sql_executor/__init__.py +0 -0
  168. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/sql_executor/executor.py +0 -0
  169. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/sql_executor/queries.py +0 -0
  170. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/sql_executor/registry.py +0 -0
  171. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/sql_executor/types.py +0 -0
  172. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/sql_executor/utils.py +0 -0
  173. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/state.py +0 -0
  174. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/utils/__init__.py +0 -0
  175. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/utils/sql_utils.py +0 -0
  176. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/utils/type_converters.py +0 -0
  177. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/scripts/git-branches.sh +0 -0
  178. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/scripts/publish.sh +0 -0
  179. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/scripts/release.sh +0 -0
  180. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/MODULE_README.md +0 -0
  181. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/__init__.py +0 -0
  182. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/conftest.py +0 -0
  183. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/MODULE_README.md +0 -0
  184. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/__init__.py +0 -0
  185. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_admin_db_columns.py +0 -0
  186. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_admin_search_negation.py +0 -0
  187. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_admin_search_sentinels.py +0 -0
  188. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_advisory_lock.py +0 -0
  189. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_agent_message.py +0 -0
  190. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_api_auth.py +0 -0
  191. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_api_config.py +0 -0
  192. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_api_handlers.py +0 -0
  193. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_api_protocol.py +0 -0
  194. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_array_agg_order_by.py +0 -0
  195. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_associations.py +0 -0
  196. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_bulk_hydration_offload.py +0 -0
  197. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_bulk_update_by_pk.py +0 -0
  198. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_bulk_upsert_increment_set_fields.py +0 -0
  199. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_cache_debug.py +0 -0
  200. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_call_function.py +0 -0
  201. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_case_when.py +0 -0
  202. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_catalog.py +0 -0
  203. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_composite_pk_filter.py +0 -0
  204. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_composite_pk_write_paths.py +0 -0
  205. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_config.py +0 -0
  206. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_conflict_target.py +0 -0
  207. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_conflict_writes.py +0 -0
  208. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_connection_codecs.py +0 -0
  209. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_connection_poison_guard.py +0 -0
  210. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_connection_primitives.py +0 -0
  211. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_count_composite_distinct.py +0 -0
  212. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_db_function_field.py +0 -0
  213. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_ddl_generator.py +0 -0
  214. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_derived_table.py +0 -0
  215. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_dirty_tracking.py +0 -0
  216. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_dynamic_crud.py +0 -0
  217. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_exceptions.py +0 -0
  218. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_execute_query_integrity_mapping.py +0 -0
  219. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_expression_primitives.py +0 -0
  220. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_fields.py +0 -0
  221. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_filter_jsonb_agg_array_index.py +0 -0
  222. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_from_alias.py +0 -0
  223. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_fts.py +0 -0
  224. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_func_expression_args.py +0 -0
  225. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_graph_walk.py +0 -0
  226. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_having_annotate_alias.py +0 -0
  227. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_insert_ignore_and_admin_primitives.py +0 -0
  228. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_introspect_rls_policies.py +0 -0
  229. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_join_and_aggregate.py +0 -0
  230. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_latest_rows.py +0 -0
  231. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_listen_notify.py +0 -0
  232. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_managed_write_guard.py +0 -0
  233. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_matrx_entity.py +0 -0
  234. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_migration_diff_types.py +0 -0
  235. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_migration_loader.py +0 -0
  236. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_model_instance.py +0 -0
  237. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_model_meta.py +0 -0
  238. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_optimistic_concurrency.py +0 -0
  239. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_order_by_expression.py +0 -0
  240. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_parameter_name_hydration.py +0 -0
  241. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_patch_jsonb_path.py +0 -0
  242. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_pool_holder_census.py +0 -0
  243. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_pool_loop_recreation.py +0 -0
  244. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_pool_maintenance.py +0 -0
  245. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_postgrest_filters.py +0 -0
  246. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_pydantic_bridge.py +0 -0
  247. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_query_builder.py +0 -0
  248. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_query_error_detail.py +0 -0
  249. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_query_executor_sql.py +0 -0
  250. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_query_timeout.py +0 -0
  251. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_querybuilder_clone_semantics.py +0 -0
  252. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_queue_claim.py +0 -0
  253. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_registry.py +0 -0
  254. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_registry_multi_database.py +0 -0
  255. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_registry_multi_schema.py +0 -0
  256. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_relational_backfill.py +0 -0
  257. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_relations.py +0 -0
  258. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_resilience.py +0 -0
  259. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_rls_session_scope.py +0 -0
  260. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_schema_exists.py +0 -0
  261. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_session_advisory_lock.py +0 -0
  262. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_state_cache.py +0 -0
  263. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_subquery_filter_raw.py +0 -0
  264. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_subquery_in_filter.py +0 -0
  265. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_supabase_auth.py +0 -0
  266. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_supabase_config.py +0 -0
  267. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_tls.py +0 -0
  268. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_typed_join_projection.py +0 -0
  269. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_typed_predicates.py +0 -0
  270. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_update_case_expression.py +0 -0
  271. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_update_subquery.py +0 -0
  272. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_update_with_rebase.py +0 -0
  273. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_upsert_default_leak.py +0 -0
  274. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_upsert_with_conflict.py +0 -0
  275. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_values_jsonb_decode.py +0 -0
  276. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_watched_lifecycle.py +0 -0
  277. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_write_retry_semantics.py +0 -0
  278. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level2/MODULE_README.md +0 -0
  279. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level2/__init__.py +0 -0
  280. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level2/conftest.py +0 -0
  281. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level2/test_bulk_ops.py +0 -0
  282. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level2/test_cache_integration.py +0 -0
  283. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level2/test_crud.py +0 -0
  284. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level2/test_foreign_keys.py +0 -0
  285. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level2/test_m2m.py +0 -0
  286. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level2/test_manager.py +0 -0
  287. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level2/test_migrations_live.py +0 -0
  288. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level2/test_query_execution.py +0 -0
  289. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level2/test_schema_diff.py +0 -0
  290. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/sample_project/.env.example +0 -0
  291. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/sample_project/README.md +0 -0
  292. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/sample_project/__init__.py +0 -0
  293. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/sample_project/generate.py +0 -0
  294. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/sample_project/generated/.gitkeep +0 -0
  295. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/sample_project/matrx_orm.yaml +0 -0
  296. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/sample_project/test_schema_generation.py +0 -0
  297. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/sample_project_desktop/.env.example +0 -0
  298. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/sample_project_desktop/README.md +0 -0
  299. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/sample_project_desktop/__init__.py +0 -0
  300. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/sample_project_desktop/client_example.py +0 -0
  301. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/sample_project_desktop/client_example.ts +0 -0
  302. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/sample_project_desktop/client_supabase_example.py +0 -0
  303. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/sample_project_desktop/server.py +0 -0
  304. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/schema/entity_tests.py +0 -0
  305. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/schema/test_base_generation.py +0 -0
  306. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/schema/test_composite_pk_fk_generation.py +0 -0
  307. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/schema/test_composite_unique_generation.py +0 -0
  308. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/schema/test_dto_identity_generation.py +0 -0
  309. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/schema/test_entity_capability_generation.py +0 -0
  310. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/schema/test_generate_schema.py +0 -0
  311. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/schema/test_junction_analysis.py +0 -0
  312. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/schema/test_multi_schema_output.py +0 -0
  313. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/schema/test_sql_expression_defaults.py +0 -0
  314. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/schema/test_table_relationships_query.py +0 -0
  315. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/schema/test_view_generation.py +0 -0
  316. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/schema_builder/test_array_default_parsing.py +0 -0
  317. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/schema_builder/test_focused_generation_plan.py +0 -0
  318. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/schema_builder/test_host_models_aggregator.py +0 -0
  319. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/session/__init__.py +0 -0
  320. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/session/conftest.py +0 -0
  321. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/session/test_cache_no_downgrade.py +0 -0
  322. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/session/test_capture_poison_proof.py +0 -0
  323. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/session/test_coalesce.py +0 -0
  324. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/session/test_dag.py +0 -0
  325. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/session/test_disk_spill_fallback.py +0 -0
  326. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/session/test_flush_individual_fallback.py +0 -0
  327. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/session/test_governed_write.py +0 -0
  328. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/session/test_managed.py +0 -0
  329. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/session/test_op_enqueue_site.py +0 -0
  330. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/session/test_reads.py +0 -0
  331. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/session/test_session.py +0 -0
  332. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/test_database_name_alias.py +0 -0
  333. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/test_db_functions.py +0 -0
  334. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/test_model_cls_refactor.py +0 -0
  335. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/test_no_blocking_asyncpg_tls.py +0 -0
  336. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/test_one_database.py +0 -0
  337. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/test_secrets_battery.py +0 -0
  338. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/test_sql_param_casts.py +0 -0
  339. {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/test_sync_twins_guard.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: matrx-orm
3
- Version: 3.1.75
3
+ Version: 3.1.76
4
4
  Summary: Async-first PostgreSQL ORM with bidirectional migrations, schema introspection, many-to-many relationships, and built-in state caching
5
5
  Project-URL: Homepage, https://github.com/AI-Matrix-Engine/aidream-current
6
6
  Project-URL: Repository, https://github.com/AI-Matrix-Engine/aidream-current
@@ -25,7 +25,7 @@ Requires-Python: >=3.13
25
25
  Requires-Dist: asyncpg>=0.31.0
26
26
  Requires-Dist: cryptography>=43.0
27
27
  Requires-Dist: gitpython>=3.1.40
28
- Requires-Dist: matrx-utils>=2.0.16
28
+ Requires-Dist: matrx-utils>=2.0.17
29
29
  Requires-Dist: psycopg-pool>=3.2.5
30
30
  Requires-Dist: psycopg[binary]>=3.2.5
31
31
  Requires-Dist: python-dotenv>=1.0
@@ -82,13 +82,36 @@ commit; exceptions roll back; cancellation-shielded teardown finishes before
82
82
  the connection returns to the pool. No path can leave `authenticated` on a
83
83
  reused server connection.
84
84
 
85
+ **Empty-to-nonempty is not contamination proof.** A READ COMMITTED row can be
86
+ created between the original empty query and its reproof (`guest_executions`
87
+ does this under concurrent first requests). `reprove_empty_read` captures the
88
+ pinned server session's identity before and after `RESET SESSION AUTHORIZATION`
89
+ + `RESET ROLE`, returns any newly visible rows, and emits
90
+ `pooler_role_contamination` only when that identity change directly proves a
91
+ borrowed role. This also repairs both `SET ROLE` and `SET SESSION AUTHORIZATION`;
92
+ `RESET ROLE` alone does not repair the latter.
93
+
94
+ **The destructive live proof is loopback-only.**
95
+ `tests/live_pooler_role.py::assert_isolated_pooler_target` runs before DSN
96
+ construction or connection and refuses every shared or remote database. The proof
97
+ deliberately contaminates server connections; repeated `RESET ROLE` statements are
98
+ probabilistic cleanup on a transaction pooler, never isolation. Run it only by
99
+ pointing the existing `SUPABASE_MATRIX_*` values at a loopback Postgres +
100
+ transaction-pooler test stack.
101
+
85
102
  Pinned by `tests/level1/test_rls_session_scope.py` and
86
- `tests/level1/test_connection_poison_guard.py`.
103
+ `tests/level1/test_connection_poison_guard.py`, plus
104
+ `tests/test_pooler_role_guard.py` for the live-proof boundary.
87
105
 
88
- Verified against code: 2026-08-15.
106
+ Verified against code: 2026-08-17.
89
107
 
90
108
  ## Change log
91
109
 
110
+ - 2026-08-17: Required direct before/after role evidence before recording
111
+ contamination; concurrent empty-to-nonempty reads reconcile without a false
112
+ repair-queue row, and session-authorization contamination is reset too.
113
+ - 2026-08-17: Refused destructive pooler-role verification against shared or
114
+ remote databases before any connection is opened.
92
115
  - 2026-08-16: Kept the destructive transaction-pool contamination proof out of
93
116
  the installable runtime package and isolated it under package tests; the
94
117
  operator wrapper is SQL-free and runtime prose no longer resembles an
@@ -37,10 +37,12 @@ TWO INDEPENDENT LAYERS, each sufficient alone, each screaming when it fires:
37
37
 
38
38
  2. **Reconciliation — an empty read is re-proved on a session that can prove its
39
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)
41
- that starts with ``RESET ROLE``. If the rows are really there, the caller gets
42
- the TRUE rows and the lie is reported through every registered sink. If the
43
- session cannot be made clean, it RAISES rather than handing back an absence.
40
+ is re-run inside an explicit transaction (pinned to ONE server connection).
41
+ The pinned session's identity is captured before and after resetting session
42
+ authorization and role. The caller gets any rows that appeared, but a
43
+ contamination alarm is emitted only when the role change was directly
44
+ observed. If the session cannot be made clean, it RAISES rather than handing
45
+ back an absence.
44
46
 
45
47
  Layer 2 follows the repo's guard doctrine: a guard that CAN reconcile MUST
46
48
  reconcile — we hold the correct answer, so killing the caller's request would be
@@ -98,7 +100,7 @@ class PoolerRoleContamination(RuntimeError):
98
100
 
99
101
 
100
102
  class EmptyReadContaminationEvent(BaseModel):
101
- """A read returned zero rows because the session was wearing a borrowed role."""
103
+ """An empty-read reproof directly observed and repaired a borrowed role."""
102
104
 
103
105
  model_config = ConfigDict(extra="forbid", frozen=True)
104
106
 
@@ -166,14 +168,14 @@ def assert_no_session_role_change(query: str) -> None:
166
168
 
167
169
 
168
170
  async def emit_contamination(event: EmptyReadContaminationEvent) -> None:
169
- """Scream about a proven silent-empty read, durably record it, fan out to sinks."""
171
+ """Scream about a directly observed borrowed role and fan out to sinks."""
170
172
  vcprint(
171
- "POOLER ROLE CONTAMINATION — an ORM read returned ZERO ROWS that were "
172
- "really there.\n"
173
+ "POOLER ROLE CONTAMINATION — an empty-read reproof directly observed "
174
+ "and repaired a borrowed server role.\n"
173
175
  f" config: {event.config_name}\n"
174
- f" session ran as: {event.contaminated_role} (login role "
176
+ f" pooled session was: {event.contaminated_role} (clean login role "
175
177
  f"{event.login_role})\n"
176
- f" true row count on a clean session: {event.true_row_count}\n"
178
+ f" cleaned query row count: {event.true_row_count}\n"
177
179
  f" query: {event.query[:300]}\n"
178
180
  " The caller has been given the TRUE rows. Something on this Postgres "
179
181
  "tenant issued a non-LOCAL SET ROLE; every other client is exposed until "
@@ -182,7 +184,7 @@ async def emit_contamination(event: EmptyReadContaminationEvent) -> None:
182
184
  style="bold",
183
185
  )
184
186
  logger.error(
185
- "[pooler-role] silent empty read repaired on %s (ran as %s, %d true rows): %s",
187
+ "[pooler-role] borrowed pooled role repaired on %s (observed %s, %d clean rows): %s",
186
188
  event.config_name,
187
189
  event.contaminated_role,
188
190
  event.true_row_count,
@@ -195,8 +197,9 @@ async def emit_contamination(event: EmptyReadContaminationEvent) -> None:
195
197
 
196
198
  await record_error(
197
199
  PoolerRoleContamination(
198
- f"read returned 0 rows as '{event.contaminated_role}'; "
199
- f"{event.true_row_count} rows were really there"
200
+ f"reproof observed pooled role '{event.contaminated_role}' "
201
+ f"(login role '{event.login_role}'); cleaned read returned "
202
+ f"{event.true_row_count} rows"
200
203
  ),
201
204
  kind="pooler_role_contamination",
202
205
  source_app="matrx-orm",
@@ -241,10 +244,18 @@ async def reprove_empty_read(
241
244
  last_role: tuple[str, str] | None = None
242
245
  for _ in range(REPROVE_ATTEMPTS):
243
246
  async with conn.transaction():
247
+ before = await conn.fetchrow("SELECT current_user, session_user")
248
+ before_current = before["current_user"]
249
+ before_session = before["session_user"]
250
+
251
+ # RESET ROLE alone cannot repair SET SESSION AUTHORIZATION: in that
252
+ # state current_user and session_user can agree on the WRONG identity.
253
+ # Reset both while this transaction pins the exact server connection.
254
+ await conn.execute("RESET SESSION AUTHORIZATION")
244
255
  await conn.execute("RESET ROLE")
245
- identity = await conn.fetchrow("SELECT current_user, session_user")
246
- current = identity["current_user"]
247
- login = identity["session_user"]
256
+ after = await conn.fetchrow("SELECT current_user, session_user")
257
+ current = after["current_user"]
258
+ login = after["session_user"]
248
259
  if current != login:
249
260
  last_role = (current, login)
250
261
  continue
@@ -253,18 +264,24 @@ async def reprove_empty_read(
253
264
  return None
254
265
  true_rows = [normalize(r) for r in rows]
255
266
 
256
- await emit_contamination(
257
- EmptyReadContaminationEvent(
258
- config_name=config_name,
259
- query=query,
260
- # The first read ran on a session we could not observe directly;
261
- # what we can state is the role it was NOT running as.
262
- contaminated_role="<not the login role>" if last_role is None else last_role[0],
263
- login_role=login,
264
- true_row_count=len(true_rows),
265
- occurred_at=datetime.now(UTC),
267
+ observed_contamination = (before_current, before_session) != (current, login)
268
+
269
+ # Rows may legitimately appear between two READ COMMITTED statements.
270
+ # guest_executions is the canonical example: concurrent first requests
271
+ # for one fingerprint race a lookup against creation. Empty -> non-empty
272
+ # is reconciliation, not proof of pool contamination. Only the captured
273
+ # before/after identity change can put this class in the repair queue.
274
+ if observed_contamination:
275
+ await emit_contamination(
276
+ EmptyReadContaminationEvent(
277
+ config_name=config_name,
278
+ query=query,
279
+ contaminated_role=before_current,
280
+ login_role=login,
281
+ true_row_count=len(true_rows),
282
+ occurred_at=datetime.now(UTC),
283
+ )
266
284
  )
267
- )
268
285
  return true_rows
269
286
 
270
287
  current, login = last_role if last_role is not None else ("<unknown>", "<unknown>")
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "matrx-orm"
3
- version = "3.1.75"
3
+ version = "3.1.76"
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.16",
44
+ "matrx-utils>=2.0.17",
45
45
  "pyyaml>=6.0.3",
46
46
  "python-dotenv>=1.0",
47
47
  "cryptography>=43.0",
@@ -2,19 +2,46 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ from ipaddress import ip_address
5
6
  from typing import Any
6
7
 
7
8
  from matrx_orm.core import pooler_role
8
9
  from matrx_orm.core.async_db_manager import AsyncDatabaseManager
10
+ from matrx_orm.core.config import get_database_config
9
11
  from matrx_orm.core.pooler_role import SessionRoleChangeRefused, register_contamination_sink
10
12
  from matrx_orm.core.tls import resolve_asyncpg_ssl_from_dsn
11
13
  from matrx_orm.platform_db import platform_connection_url
12
14
 
13
15
 
16
+ class UnsafePoolerVerificationTarget(RuntimeError):
17
+ pass
18
+
19
+
20
+ def assert_isolated_pooler_target(config_name: str) -> None:
21
+ config = get_database_config(config_name)
22
+ host = str(config.get("host") or "").strip().lower().rstrip(".")
23
+ address = host[1:-1] if host.startswith("[") and host.endswith("]") else host
24
+ try:
25
+ is_loopback = ip_address(address).is_loopback
26
+ except ValueError:
27
+ is_loopback = address == "localhost"
28
+ if is_loopback:
29
+ return
30
+ raise UnsafePoolerVerificationTarget(
31
+ "DESTRUCTIVE pooler-role verification REFUSED: the configured database host "
32
+ f"{host!r} is not loopback. This proof deliberately leaves server connections "
33
+ "wearing a borrowed role; a best-effort RESET ROLE sweep cannot make a shared "
34
+ "or remote transaction pool safe again. Point the existing SUPABASE_MATRIX_* "
35
+ "values at a local Postgres + transaction-pooler test stack, then retry."
36
+ )
37
+
38
+
14
39
  async def run_live_verification(
15
40
  *, config_name: str, table: str, contaminate: int, iterations: int, baseline: bool
16
41
  ) -> int:
17
- """Contaminate a shared pool deliberately, prove both guards, then scrub it."""
42
+ """Contaminate an isolated local pool, prove both guards, then scrub it."""
43
+ assert_isolated_pooler_target(config_name)
44
+
18
45
  import asyncpg
19
46
 
20
47
  dsn = platform_connection_url(package="verify_pooler_role_guard")
@@ -19,6 +19,7 @@ from matrx_orm.core.pooler_role import (
19
19
  reprove_empty_read,
20
20
  reset_contamination_sinks,
21
21
  )
22
+ from . import live_pooler_role
22
23
 
23
24
  REFUSED = [
24
25
  "SET ROLE authenticated",
@@ -104,26 +105,62 @@ async def test_genuinely_empty_read_stays_empty_and_is_silent() -> None:
104
105
  events: list[EmptyReadContaminationEvent] = []
105
106
  register_contamination_sink(events.append)
106
107
 
107
- conn = _FakeConn(identities=[("postgres", "postgres")], rows=[])
108
+ conn = _FakeConn(
109
+ identities=[("postgres", "postgres"), ("postgres", "postgres")],
110
+ rows=[],
111
+ )
108
112
  assert await _reprove(conn) is None
109
113
  assert events == []
114
+ assert "RESET SESSION AUTHORIZATION" in conn.log
110
115
  assert "RESET ROLE" in conn.log
111
116
  reset_contamination_sinks()
112
117
 
113
118
 
114
119
  @pytest.mark.asyncio
115
- async def test_empty_read_that_was_a_lie_returns_the_true_rows_and_screams() -> None:
120
+ async def test_concurrent_insert_is_reconciled_without_a_false_contamination_alarm() -> None:
121
+ reset_contamination_sinks()
122
+ events: list[EmptyReadContaminationEvent] = []
123
+ register_contamination_sink(events.append)
124
+
125
+ conn = _FakeConn(
126
+ identities=[("postgres", "postgres"), ("postgres", "postgres")],
127
+ rows=[{"id": "guest-row"}],
128
+ )
129
+ rows = await _reprove(conn)
130
+
131
+ assert rows == [{"id": "guest-row"}]
132
+ assert events == []
133
+ reset_contamination_sinks()
134
+
135
+
136
+ @pytest.mark.asyncio
137
+ @pytest.mark.parametrize(
138
+ "before",
139
+ [
140
+ ("authenticated", "postgres"),
141
+ ("authenticated", "authenticated"),
142
+ ],
143
+ ids=["set-role", "set-session-authorization"],
144
+ )
145
+ async def test_observed_role_contamination_returns_true_rows_and_screams(
146
+ before: tuple[str, str],
147
+ ) -> None:
116
148
  reset_contamination_sinks()
117
149
  events: list[EmptyReadContaminationEvent] = []
118
150
  register_contamination_sink(events.append)
119
151
 
120
- conn = _FakeConn(identities=[("postgres", "postgres")], rows=[{"id": "a"}, {"id": "b"}])
152
+ conn = _FakeConn(
153
+ identities=[before, ("postgres", "postgres")],
154
+ rows=[{"id": "a"}, {"id": "b"}],
155
+ )
121
156
  rows = await _reprove(conn)
122
157
 
123
158
  assert rows == [{"id": "a"}, {"id": "b"}]
124
159
  assert len(events) == 1
125
160
  assert events[0].true_row_count == 2
126
161
  assert events[0].config_name == "test_db"
162
+ assert events[0].contaminated_role == "authenticated"
163
+ assert events[0].login_role == "postgres"
127
164
  reset_contamination_sinks()
128
165
 
129
166
 
@@ -131,7 +168,11 @@ async def test_empty_read_that_was_a_lie_returns_the_true_rows_and_screams() ->
131
168
  async def test_a_session_that_never_comes_clean_raises_instead_of_reporting_absence() -> None:
132
169
  reset_contamination_sinks()
133
170
  conn = _FakeConn(
134
- identities=[("authenticated", "postgres")] * 5,
171
+ identities=[
172
+ identity
173
+ for _ in range(5)
174
+ for identity in (("authenticated", "postgres"), ("authenticated", "postgres"))
175
+ ],
135
176
  rows=[],
136
177
  )
137
178
  with pytest.raises(PoolerRoleContamination) as excinfo:
@@ -140,3 +181,44 @@ async def test_a_session_that_never_comes_clean_raises_instead_of_reporting_abse
140
181
  # failure, never data.
141
182
  assert "not absence" in str(excinfo.value)
142
183
  reset_contamination_sinks()
184
+
185
+
186
+ @pytest.mark.parametrize("host", ["localhost", "localhost.", "127.0.0.1", "::1", "[::1]"])
187
+ def test_destructive_live_proof_accepts_only_loopback_hosts(
188
+ host: str, monkeypatch: pytest.MonkeyPatch
189
+ ) -> None:
190
+ monkeypatch.setattr(
191
+ live_pooler_role,
192
+ "get_database_config",
193
+ lambda _name: {"host": host},
194
+ )
195
+
196
+ live_pooler_role.assert_isolated_pooler_target("test_db")
197
+
198
+
199
+ @pytest.mark.asyncio
200
+ async def test_destructive_live_proof_refuses_remote_before_connecting(
201
+ monkeypatch: pytest.MonkeyPatch,
202
+ ) -> None:
203
+ monkeypatch.setattr(
204
+ live_pooler_role,
205
+ "get_database_config",
206
+ lambda _name: {"host": "aws-1-us-west-1.pooler.supabase.com"},
207
+ )
208
+
209
+ def unexpected_dsn(**_kwargs: object) -> str:
210
+ pytest.fail("the verifier tried to build a DSN for a remote target")
211
+
212
+ monkeypatch.setattr(live_pooler_role, "platform_connection_url", unexpected_dsn)
213
+
214
+ with pytest.raises(
215
+ live_pooler_role.UnsafePoolerVerificationTarget,
216
+ match="DESTRUCTIVE pooler-role verification REFUSED",
217
+ ):
218
+ await live_pooler_role.run_live_verification(
219
+ config_name="test_db",
220
+ table="public.example",
221
+ contaminate=1,
222
+ iterations=1,
223
+ baseline=False,
224
+ )
File without changes
File without changes
File without changes
File without changes
File without changes