matrx-orm 3.1.48__tar.gz → 3.1.49__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 (323) hide show
  1. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/.gitignore +2 -0
  2. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/CLAUDE.md +2 -1
  3. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/PKG-INFO +1 -1
  4. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/__init__.py +14 -0
  5. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/core/async_db_manager.py +43 -2
  6. matrx_orm-3.1.49/matrx_orm/core/one_database.py +210 -0
  7. matrx_orm-3.1.49/matrx_orm/platform_db.py +138 -0
  8. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/query/FEATURE.md +8 -0
  9. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/query/executor.py +6 -0
  10. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/secrets_battery/crypto.py +1 -1
  11. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/pyproject.toml +1 -1
  12. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_join_and_aggregate.py +8 -0
  13. matrx_orm-3.1.49/tests/test_one_database.py +105 -0
  14. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/AGENTS.md +0 -0
  15. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/BASE_CLASS_METHODS.md +0 -0
  16. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/MODEL_API.md +0 -0
  17. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/README.md +0 -0
  18. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/database/orm/extended/managers/ai_model_base.py +0 -0
  19. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/docs/migrations.md +0 -0
  20. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/MODULE_README.md +0 -0
  21. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/adapters/MODULE_README.md +0 -0
  22. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/adapters/__init__.py +0 -0
  23. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/adapters/async_postgresql.py +0 -0
  24. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/adapters/base_adapter.py +0 -0
  25. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/adapters/postgresql.py +0 -0
  26. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/adapters/postgrest_client_adapter.py +0 -0
  27. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/adapters/supabase_adapter.py +0 -0
  28. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/admin/__init__.py +0 -0
  29. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/admin/router.py +0 -0
  30. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/api/__init__.py +0 -0
  31. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/api/auth.py +0 -0
  32. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/api/config.py +0 -0
  33. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/api/handlers.py +0 -0
  34. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/api/protocol.py +0 -0
  35. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/api/server.py +0 -0
  36. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/associations.py +0 -0
  37. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/cache_debug.py +0 -0
  38. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/catalog.py +0 -0
  39. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/client/MODULE_README.md +0 -0
  40. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/client/__init__.py +0 -0
  41. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/client/postgres_connection.py +0 -0
  42. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/client/postgrest.py +0 -0
  43. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/client/supabase_auth.py +0 -0
  44. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/client/supabase_config.py +0 -0
  45. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/client/supabase_manager.py +0 -0
  46. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/core/EXTENDED-TASK.md +0 -0
  47. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/core/FEATURE.md +0 -0
  48. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/core/MODULE_README.md +0 -0
  49. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/core/RELATIONS-TASKS.md +0 -0
  50. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/core/TASKS.md +0 -0
  51. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/core/__init__.py +0 -0
  52. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/core/base.py +0 -0
  53. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/core/config.py +0 -0
  54. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/core/connections.py +0 -0
  55. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/core/db_function.py +0 -0
  56. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/core/diagnostics.py +0 -0
  57. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/core/expressions.py +0 -0
  58. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/core/extended.py +0 -0
  59. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/core/fields.py +0 -0
  60. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/core/introspection.py +0 -0
  61. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/core/listen.py +0 -0
  62. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/core/loop_filters.py +0 -0
  63. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/core/model_dto.py +0 -0
  64. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/core/model_view.py +0 -0
  65. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/core/paginator.py +0 -0
  66. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/core/pool_maintenance.py +0 -0
  67. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/core/pydantic_bridge.py +0 -0
  68. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/core/registry.py +0 -0
  69. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/core/relations.py +0 -0
  70. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/core/resilience.py +0 -0
  71. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/core/rls_session.py +0 -0
  72. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/core/signals.py +0 -0
  73. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/core/tls.py +0 -0
  74. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/core/transaction.py +0 -0
  75. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/core/types.py +0 -0
  76. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/core/write_queue.py +0 -0
  77. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/entity.py +0 -0
  78. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/error_handling.py +0 -0
  79. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/exceptions.py +0 -0
  80. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/extended/__init__.py +0 -0
  81. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/extended/app_error_handler.py +0 -0
  82. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/identity_rule.py +0 -0
  83. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/middleware/__init__.py +0 -0
  84. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/middleware/base.py +0 -0
  85. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/migrations/MODULE_README.md +0 -0
  86. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/migrations/__init__.py +0 -0
  87. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/migrations/cli.py +0 -0
  88. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/migrations/ddl.py +0 -0
  89. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/migrations/diff.py +0 -0
  90. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/migrations/executor.py +0 -0
  91. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/migrations/integration.py +0 -0
  92. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/migrations/loader.py +0 -0
  93. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/migrations/operations.py +0 -0
  94. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/migrations/state.py +0 -0
  95. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/migrations/table_filter.py +0 -0
  96. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/operations/MODULE_README.md +0 -0
  97. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/operations/__init__.py +0 -0
  98. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/operations/bulk_update_values.py +0 -0
  99. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/operations/conflict.py +0 -0
  100. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/operations/conflict_writes.py +0 -0
  101. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/operations/create.py +0 -0
  102. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/operations/db_functions.py +0 -0
  103. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/operations/delete.py +0 -0
  104. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/operations/dynamic_admin.py +0 -0
  105. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/operations/dynamic_crud.py +0 -0
  106. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/operations/graph_walk.py +0 -0
  107. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/operations/latest_rows.py +0 -0
  108. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/operations/queue_claim.py +0 -0
  109. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/operations/read.py +0 -0
  110. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/operations/relational_backfill.py +0 -0
  111. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/operations/staging_load.py +0 -0
  112. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/operations/update.py +0 -0
  113. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/py.typed +0 -0
  114. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/python_sql/MODULE_README.md +0 -0
  115. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/python_sql/__init__.py +0 -0
  116. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/python_sql/db_objects.py +0 -0
  117. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/python_sql/table_detailed_relationships.py +0 -0
  118. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/python_sql/table_typescript_relationship.py +0 -0
  119. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/query/MODULE_README.md +0 -0
  120. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/query/__init__.py +0 -0
  121. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/query/builder.py +0 -0
  122. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/schema_builder/MODULE_README.md +0 -0
  123. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/schema_builder/__init__.py +0 -0
  124. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/schema_builder/code_handler.py +0 -0
  125. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/schema_builder/codegen_writer.py +0 -0
  126. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/schema_builder/columns.py +0 -0
  127. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/schema_builder/common.py +0 -0
  128. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/schema_builder/diff_preview.py +0 -0
  129. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/schema_builder/entity_capabilities.py +0 -0
  130. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/schema_builder/generator.py +0 -0
  131. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/schema_builder/helpers/__init__.py +0 -0
  132. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/schema_builder/helpers/base_generators.py +0 -0
  133. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/schema_builder/helpers/entity_generators.py +0 -0
  134. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/schema_builder/helpers/git_checker.py +0 -0
  135. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/schema_builder/package_wiring.py +0 -0
  136. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/schema_builder/relationships.py +0 -0
  137. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/schema_builder/runner.py +0 -0
  138. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/schema_builder/schema.py +0 -0
  139. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/schema_builder/schema_manager.py +0 -0
  140. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/schema_builder/tables.py +0 -0
  141. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/schema_builder/views.py +0 -0
  142. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/secrets_battery/FEATURE.md +0 -0
  143. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/secrets_battery/__init__.py +0 -0
  144. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/secrets_battery/items.py +0 -0
  145. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/secrets_battery/model.py +0 -0
  146. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/secrets_battery/org_service.py +0 -0
  147. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/secrets_battery/service.py +0 -0
  148. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/session/README.md +0 -0
  149. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/session/__init__.py +0 -0
  150. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/session/coalesce.py +0 -0
  151. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/session/dag.py +0 -0
  152. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/session/errors.py +0 -0
  153. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/session/fallback.py +0 -0
  154. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/session/flush.py +0 -0
  155. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/session/lifecycle.py +0 -0
  156. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/session/managed.py +0 -0
  157. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/session/op.py +0 -0
  158. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/session/reads.py +0 -0
  159. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/session/session.py +0 -0
  160. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/session/telemetry.py +0 -0
  161. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/sql_executor/MODULE_README.md +0 -0
  162. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/sql_executor/__init__.py +0 -0
  163. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/sql_executor/executor.py +0 -0
  164. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/sql_executor/queries.py +0 -0
  165. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/sql_executor/registry.py +0 -0
  166. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/sql_executor/types.py +0 -0
  167. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/sql_executor/utils.py +0 -0
  168. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/state.py +0 -0
  169. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/utils/__init__.py +0 -0
  170. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/utils/sql_utils.py +0 -0
  171. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/matrx_orm/utils/type_converters.py +0 -0
  172. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/scripts/git-branches.sh +0 -0
  173. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/scripts/publish.sh +0 -0
  174. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/scripts/release.sh +0 -0
  175. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/MODULE_README.md +0 -0
  176. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/__init__.py +0 -0
  177. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/conftest.py +0 -0
  178. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/MODULE_README.md +0 -0
  179. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/__init__.py +0 -0
  180. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_admin_db_columns.py +0 -0
  181. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_admin_search_negation.py +0 -0
  182. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_admin_search_sentinels.py +0 -0
  183. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_advisory_lock.py +0 -0
  184. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_agent_message.py +0 -0
  185. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_api_auth.py +0 -0
  186. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_api_config.py +0 -0
  187. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_api_handlers.py +0 -0
  188. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_api_protocol.py +0 -0
  189. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_array_agg_order_by.py +0 -0
  190. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_associations.py +0 -0
  191. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_bulk_update_by_pk.py +0 -0
  192. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_bulk_upsert_increment_set_fields.py +0 -0
  193. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_cache_debug.py +0 -0
  194. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_call_function.py +0 -0
  195. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_case_when.py +0 -0
  196. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_catalog.py +0 -0
  197. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_composite_pk_filter.py +0 -0
  198. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_composite_pk_write_paths.py +0 -0
  199. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_config.py +0 -0
  200. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_conflict_target.py +0 -0
  201. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_conflict_writes.py +0 -0
  202. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_connection_codecs.py +0 -0
  203. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_connection_poison_guard.py +0 -0
  204. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_connection_primitives.py +0 -0
  205. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_count_composite_distinct.py +0 -0
  206. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_db_function_field.py +0 -0
  207. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_ddl_generator.py +0 -0
  208. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_derived_table.py +0 -0
  209. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_dirty_tracking.py +0 -0
  210. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_dynamic_crud.py +0 -0
  211. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_exceptions.py +0 -0
  212. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_execute_query_integrity_mapping.py +0 -0
  213. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_expression_primitives.py +0 -0
  214. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_fields.py +0 -0
  215. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_filter_jsonb_agg_array_index.py +0 -0
  216. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_from_alias.py +0 -0
  217. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_fts.py +0 -0
  218. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_func_expression_args.py +0 -0
  219. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_graph_walk.py +0 -0
  220. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_having_annotate_alias.py +0 -0
  221. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_insert_ignore_and_admin_primitives.py +0 -0
  222. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_introspect_rls_policies.py +0 -0
  223. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_latest_rows.py +0 -0
  224. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_listen_notify.py +0 -0
  225. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_managed_write_guard.py +0 -0
  226. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_matrx_entity.py +0 -0
  227. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_migration_diff_types.py +0 -0
  228. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_migration_loader.py +0 -0
  229. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_model_instance.py +0 -0
  230. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_model_meta.py +0 -0
  231. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_order_by_expression.py +0 -0
  232. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_patch_jsonb_path.py +0 -0
  233. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_pool_loop_recreation.py +0 -0
  234. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_pool_maintenance.py +0 -0
  235. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_postgrest_filters.py +0 -0
  236. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_pydantic_bridge.py +0 -0
  237. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_query_builder.py +0 -0
  238. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_query_error_detail.py +0 -0
  239. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_query_executor_sql.py +0 -0
  240. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_query_timeout.py +0 -0
  241. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_querybuilder_clone_semantics.py +0 -0
  242. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_queue_claim.py +0 -0
  243. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_registry.py +0 -0
  244. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_registry_multi_database.py +0 -0
  245. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_registry_multi_schema.py +0 -0
  246. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_relational_backfill.py +0 -0
  247. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_relations.py +0 -0
  248. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_resilience.py +0 -0
  249. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_schema_exists.py +0 -0
  250. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_session_advisory_lock.py +0 -0
  251. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_state_cache.py +0 -0
  252. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_subquery_filter_raw.py +0 -0
  253. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_subquery_in_filter.py +0 -0
  254. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_supabase_auth.py +0 -0
  255. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_supabase_config.py +0 -0
  256. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_tls.py +0 -0
  257. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_typed_join_projection.py +0 -0
  258. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_typed_predicates.py +0 -0
  259. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_update_case_expression.py +0 -0
  260. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_update_subquery.py +0 -0
  261. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_upsert_default_leak.py +0 -0
  262. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_upsert_with_conflict.py +0 -0
  263. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_values_jsonb_decode.py +0 -0
  264. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_watched_lifecycle.py +0 -0
  265. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level1/test_write_retry_semantics.py +0 -0
  266. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level2/MODULE_README.md +0 -0
  267. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level2/__init__.py +0 -0
  268. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level2/conftest.py +0 -0
  269. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level2/test_bulk_ops.py +0 -0
  270. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level2/test_cache_integration.py +0 -0
  271. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level2/test_crud.py +0 -0
  272. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level2/test_foreign_keys.py +0 -0
  273. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level2/test_m2m.py +0 -0
  274. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level2/test_manager.py +0 -0
  275. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level2/test_migrations_live.py +0 -0
  276. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level2/test_query_execution.py +0 -0
  277. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/level2/test_schema_diff.py +0 -0
  278. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/sample_project/.env.example +0 -0
  279. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/sample_project/README.md +0 -0
  280. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/sample_project/__init__.py +0 -0
  281. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/sample_project/generate.py +0 -0
  282. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/sample_project/generated/.gitkeep +0 -0
  283. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/sample_project/matrx_orm.yaml +0 -0
  284. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/sample_project/test_schema_generation.py +0 -0
  285. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/sample_project_desktop/.env.example +0 -0
  286. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/sample_project_desktop/README.md +0 -0
  287. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/sample_project_desktop/__init__.py +0 -0
  288. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/sample_project_desktop/client_example.py +0 -0
  289. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/sample_project_desktop/client_example.ts +0 -0
  290. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/sample_project_desktop/client_supabase_example.py +0 -0
  291. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/sample_project_desktop/server.py +0 -0
  292. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/schema/entity_tests.py +0 -0
  293. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/schema/test_base_generation.py +0 -0
  294. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/schema/test_composite_pk_fk_generation.py +0 -0
  295. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/schema/test_composite_unique_generation.py +0 -0
  296. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/schema/test_dto_identity_generation.py +0 -0
  297. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/schema/test_entity_capability_generation.py +0 -0
  298. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/schema/test_generate_schema.py +0 -0
  299. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/schema/test_junction_analysis.py +0 -0
  300. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/schema/test_multi_schema_output.py +0 -0
  301. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/schema/test_sql_expression_defaults.py +0 -0
  302. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/schema/test_view_generation.py +0 -0
  303. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/schema_builder/test_focused_generation_plan.py +0 -0
  304. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/session/__init__.py +0 -0
  305. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/session/conftest.py +0 -0
  306. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/session/test_cache_no_downgrade.py +0 -0
  307. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/session/test_capture_poison_proof.py +0 -0
  308. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/session/test_coalesce.py +0 -0
  309. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/session/test_dag.py +0 -0
  310. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/session/test_disk_spill_fallback.py +0 -0
  311. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/session/test_flush_individual_fallback.py +0 -0
  312. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/session/test_governed_write.py +0 -0
  313. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/session/test_managed.py +0 -0
  314. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/session/test_op_enqueue_site.py +0 -0
  315. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/session/test_reads.py +0 -0
  316. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/session/test_session.py +0 -0
  317. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/test_database_name_alias.py +0 -0
  318. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/test_db_functions.py +0 -0
  319. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/test_model_cls_refactor.py +0 -0
  320. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/test_no_blocking_asyncpg_tls.py +0 -0
  321. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/test_secrets_battery.py +0 -0
  322. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/test_sql_param_casts.py +0 -0
  323. {matrx_orm-3.1.48 → matrx_orm-3.1.49}/tests/test_sync_twins_guard.py +0 -0
@@ -38,6 +38,8 @@ lib64/
38
38
  # Re-allow them explicitly so frontend builds don't ship without their lib layer.
39
39
  !apps/dashboard/src/lib/
40
40
  !apps/dashboard/src/lib/**
41
+ !apps/dashboard/src/features/crawler/lib/
42
+ !apps/dashboard/src/features/crawler/lib/**
41
43
  !apps/workflow-studio/src/lib/
42
44
  !apps/workflow-studio/src/lib/**
43
45
  parts/
@@ -20,6 +20,7 @@ Every table that becomes a Model needs row identity: a single-column primary key
20
20
  ## What this package provides
21
21
 
22
22
  - **Config / project registry** (`matrx_orm.core.config`): `DatabaseProjectConfig`, `register_database`, `register_database_from_env`, `get_database_config`, `get_manager_config`, `DatabaseRegistry`. A single process can run multiple named database projects concurrently.
23
+ - 🚨 **ONE CONNECTION, REQUIRED** (`matrx_orm.platform_db`): `register_platform_db(name, ...)` + `platform_connection_url()` — the ONLY sanctioned way any Matrx service or package turns env vars into a pool. One set of variables (`SUPABASE_MATRIX_*`), owned here; TLS is a deployment input (`SUPABASE_MATRIX_SSL`), never hardcoded; **RAISES** if it isn't given a database — no degraded mode. Every package's `bootstrap_db()` is one call to it. A package may absolutely run on its OWN Postgres — that is a change of VALUES, never a new variable name; what it may never have is a *second candidate* or a fallback chain. `register_database_from_env` is the low-level primitive underneath — **application and package code must not call it directly** (guard: `scripts/check_one_database.py`, blocking in release).
23
24
  - **Core model layer**: `Model`, `BaseManager`, `BaseDTO`, `ModelView`, `model_registry`, plus 50+ field types (`CharField`, `IntegerField`, `ForeignKey`, `ManyToManyField`, `JSONField`, …).
24
25
  - **`MatrxEntity`** (`matrx_orm/entity.py`) — **the platform entity contract as an ORM primitive.** A table registered in `platform.entity_types` generates as a `MatrxEntity` (everything else stays a plain `Model`) and gets the behavior instead of every consumer hand-wiring it: `await page.versions()` (not a query on `history.row_versions`), `await page.restore(3)` (not a `version_restore` RPC + a cache bust — `restore()` busts the cache itself), `Note.alive()` / `.dead()` / `.soft_delete()` / `.undelete()`, and `Page.capabilities()`. Capability flags (`_entity_token`, `_is_versioned`, `_has_soft_delete`, `_is_org_scoped`, `_rls_variant`) are **stamped by the generator from the LIVE DATABASE** (does the table really carry the `_version_capture` trigger? a `deleted_at` column?) — never from `entity_types`' own flags, which are wrong on 61 of its 242 rows. **The database is truth; a flag is a claim.** Using a capability an entity does not declare RAISES (`EntityCapabilityError`) and names the fix — it never returns silently-wrong data, and `is_deleted` refuses to answer from a column a partial fetch never loaded. Drift guard: `scripts/check_entity_drift.py` (loud, non-blocking, in `release.sh`).
25
26
  - **Change tracking** — every instance records which columns were **loaded** (a SELECTed row, or the caller's kwargs) and which were **changed**. `save()` writes only those, so `.only("id","email")` + `save()` can never NULL the columns it never fetched (it used to — silent data loss). Mutable containers (jsonb/arrays) are always rewritten when loaded, because `obj.metadata["k"]=1` cannot be seen by `__setattr__`; **reassigning** (`obj.metadata = {...}`) is tracked precisely and is the sanctioned way. `Model._hydrate()` is the ONE row→model entry point.
@@ -72,7 +73,7 @@ register_database_from_env(
72
73
 
73
74
  Inside this package:
74
75
 
75
- - **Never** call `os.environ[...]` outside `config.py` and the `schema_builder` CLI. All env-var reads must go through `register_database_from_env` with an explicit prefix.
76
+ - **Never** call `os.environ[...]` outside `config.py`, `platform_db.py` and the `schema_builder` CLI. All env-var reads go through `register_database_from_env` with an explicit prefix — and for a Matrx service that means `register_platform_database`, never a hand-picked prefix. **Never invent a new env var meaning "the database"** (`*_DATABASE_URL`, `MATRX_*_POSTGRES_*`): the name already exists and is `SUPABASE_MATRIX_*`.
76
77
  - The schema-builder tools (`ADMIN_PYTHON_ROOT`, `ADMIN_TS_ROOT`, etc.) MUST accept the same values as function kwargs; env vars are only the fallback for CLI convenience.
77
78
  - Transaction default (`MATRX_DEFAULT_DATABASE`) is acceptable but must be overrideable per-call.
78
79
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: matrx-orm
3
- Version: 3.1.48
3
+ Version: 3.1.49
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
@@ -35,8 +35,15 @@ from .core.connections import (
35
35
  open_listener_connection,
36
36
  )
37
37
  from .entity import EntityCapabilityError, MatrxEntity
38
+ from .platform_db import (
39
+ PLATFORM_DB_ENV_OVERRIDES,
40
+ PLATFORM_DB_ENV_PREFIX,
41
+ platform_connection_url,
42
+ register_platform_db,
43
+ )
38
44
  from .core.diagnostics import collect_pool_diagnostics, log_pool_diagnostics
39
45
  from .core.introspection import introspect_columns, introspect_rls_policies, schema_exists
46
+ from .core.one_database import OneDatabaseReport, PoolIdentity, audit_one_database
40
47
  from .core.listen import notify, ping
41
48
  from .core.tls import resolve_asyncpg_ssl, resolve_asyncpg_ssl_from_dsn
42
49
  from .operations.bulk_update_values import bulk_update_by_pk
@@ -333,6 +340,10 @@ __all__ = [
333
340
  "DatabaseProjectConfig",
334
341
  "register_database",
335
342
  "register_database_from_env",
343
+ "register_platform_db",
344
+ "platform_connection_url",
345
+ "PLATFORM_DB_ENV_PREFIX",
346
+ "PLATFORM_DB_ENV_OVERRIDES",
336
347
  "register_database_alias",
337
348
  "resolve_database_name",
338
349
  "is_database_registered",
@@ -358,6 +369,9 @@ __all__ = [
358
369
  "EntityCapabilityError",
359
370
  "ArrayArg",
360
371
  # ── pg_catalog introspection (read-only) ───────────────────────────────────
372
+ "audit_one_database",
373
+ "OneDatabaseReport",
374
+ "PoolIdentity",
361
375
  "introspect_columns",
362
376
  "introspect_rls_policies",
363
377
  "schema_exists",
@@ -285,11 +285,37 @@ _init_vector_codec = _init_connection_default
285
285
 
286
286
  @dataclass(frozen=True, slots=True)
287
287
  class ConnectionIdentity:
288
- """The role/database a pool is actually connected as."""
288
+ """The role/database a pool is actually connected as — asked, never assumed.
289
+
290
+ ``server_addr`` / ``server_port`` / ``cluster_id`` answer the *physical*
291
+ question "am I talking to the same Postgres as everyone else?", which env
292
+ vars cannot answer (a var set on one server and not another is invisible to
293
+ any static check). ``physical_key`` is the comparable form: two pools with
294
+ different keys are two different databases, full stop.
295
+
296
+ The physical fields are best-effort — a Unix socket has no
297
+ ``inet_server_addr()`` and ``pg_control_system()`` needs ``pg_monitor`` — so
298
+ they are optional and the key degrades gracefully rather than lying.
299
+ """
289
300
 
290
301
  current_user: str
291
302
  session_user: str
292
303
  database: str
304
+ server_addr: str | None = None
305
+ server_port: int | None = None
306
+ cluster_id: str | None = None
307
+
308
+ @property
309
+ def physical_key(self) -> str:
310
+ """The comparable identity of the physical database behind this pool.
311
+
312
+ Prefers the cluster's ``system_identifier`` (unique per Postgres data
313
+ directory) and falls back to the server address/port. The database name
314
+ is always part of the key: two logical databases in one cluster are still
315
+ two databases.
316
+ """
317
+ cluster = self.cluster_id or f"{self.server_addr or '?'}:{self.server_port or '?'}"
318
+ return f"{cluster}/{self.database}"
293
319
 
294
320
 
295
321
  class AsyncDatabaseManager:
@@ -518,12 +544,27 @@ class AsyncDatabaseManager:
518
544
  async with pool.acquire() as conn:
519
545
  row = await conn.fetchrow(
520
546
  "SELECT current_user AS current_user, session_user AS session_user, "
521
- "current_database() AS database"
547
+ "current_database() AS database, "
548
+ "host(inet_server_addr()) AS server_addr, "
549
+ "inet_server_port() AS server_port"
522
550
  )
551
+ # The cluster's system_identifier is the strongest physical-identity
552
+ # signal (unique per data directory) but needs pg_monitor, so it is
553
+ # best-effort: a NULL here just degrades physical_key to addr:port.
554
+ cluster_id: str | None = None
555
+ try:
556
+ cluster_id = await conn.fetchval(
557
+ "SELECT system_identifier::text FROM pg_control_system()"
558
+ )
559
+ except Exception: # noqa: BLE001 — insufficient privilege is normal
560
+ cluster_id = None
523
561
  return ConnectionIdentity(
524
562
  current_user=row["current_user"],
525
563
  session_user=row["session_user"],
526
564
  database=row["database"],
565
+ server_addr=row["server_addr"],
566
+ server_port=row["server_port"],
567
+ cluster_id=cluster_id,
527
568
  )
528
569
 
529
570
  @classmethod
@@ -0,0 +1,210 @@
1
+ """ONE DATABASE guard (runtime layer) — do the registered pools actually agree?
2
+
3
+ There is ONE database. The static twin (``scripts/check_one_database.py`` in the
4
+ host repo) proves the CODE resolves only ``SUPABASE_MATRIX_*``; it cannot see an
5
+ env var set on a server, a pool registered by a caller, or a hostname that points
6
+ somewhere unexpected. This layer asks the connections themselves and compares
7
+ their PHYSICAL identity — cluster ``system_identifier`` (or server address/port)
8
+ plus database name.
9
+
10
+ WHY (verified 2026-08-09): matrx-scraper's standalone service bound the LIVE,
11
+ shared ``scraper.*`` tables to a Coolify-local Postgres while ``web.*`` went to
12
+ Matrx Main — 14 rows sat in the second database, invisible to the platform, and
13
+ boot logged it as "optional legacy database registered". Two pools in one process
14
+ pointing at two different Postgres instances is the observable signature of that
15
+ bug, and this is the check that would have screamed on the first boot.
16
+
17
+ Posture (root CLAUDE.md): **scream, never crash.** A divergence is a red banner
18
+ and a durable ops record for the host to file; killing a running service over it
19
+ would trade a silent data split for an outage. Callers that genuinely need a
20
+ second database declare it (``sanctioned_separate``) — aidream's separate CMS
21
+ product database is the only one today.
22
+
23
+ Usage (host startup, or any standalone package service boot)::
24
+
25
+ from matrx_orm.core.one_database import audit_one_database
26
+
27
+ report = await audit_one_database(
28
+ platform_config_name="supabase_automation_matrix",
29
+ sanctioned_separate=("cms",),
30
+ )
31
+ if not report.ok:
32
+ ... # persist report.findings() to your ops ledger
33
+ """
34
+
35
+ from __future__ import annotations
36
+
37
+ import asyncio
38
+ from dataclasses import dataclass, field
39
+
40
+ from matrx_utils import vcprint
41
+
42
+ from matrx_orm.core.async_db_manager import AsyncDatabaseManager, ConnectionIdentity
43
+
44
+ # How long a single identity probe may take before it is recorded as a failure.
45
+ # A pool that cannot answer "who are you" at boot is itself worth reporting.
46
+ PROBE_TIMEOUT_SECONDS = 20.0
47
+
48
+
49
+ @dataclass(frozen=True)
50
+ class PoolIdentity:
51
+ """One registered matrx-orm config and the physical database it reached."""
52
+
53
+ config_name: str
54
+ identity: ConnectionIdentity | None = None
55
+ error: str | None = None
56
+
57
+ @property
58
+ def physical_key(self) -> str | None:
59
+ return self.identity.physical_key if self.identity is not None else None
60
+
61
+ def describe(self) -> str:
62
+ if self.identity is None:
63
+ return f"{self.config_name}: PROBE FAILED — {self.error}"
64
+ ident = self.identity
65
+ where = f"{ident.server_addr or '?'}:{ident.server_port or '?'}"
66
+ cluster = f" cluster={ident.cluster_id}" if ident.cluster_id else ""
67
+ return f"{self.config_name}: {ident.database} @ {where}{cluster} as {ident.current_user}"
68
+
69
+
70
+ @dataclass(frozen=True)
71
+ class OneDatabaseReport:
72
+ """The result of comparing every registered pool against the platform pool."""
73
+
74
+ platform_config: str
75
+ platform: PoolIdentity | None
76
+ pools: list[PoolIdentity] = field(default_factory=list)
77
+ divergent: list[PoolIdentity] = field(default_factory=list)
78
+ probe_failures: list[PoolIdentity] = field(default_factory=list)
79
+ sanctioned_separate: tuple[str, ...] = ()
80
+
81
+ @property
82
+ def ok(self) -> bool:
83
+ """True when every non-sanctioned pool reached the same physical database."""
84
+ return (
85
+ not self.divergent and self.platform is not None and self.platform.identity is not None
86
+ )
87
+
88
+ def findings(self) -> list[dict[str, str]]:
89
+ """Flat, persistable records — one per divergent pool, for an ops ledger."""
90
+ platform_key = self.platform.physical_key if self.platform else None
91
+ out: list[dict[str, str]] = []
92
+ for pool in self.divergent:
93
+ out.append(
94
+ {
95
+ "config_name": pool.config_name,
96
+ "physical_key": pool.physical_key or "<unknown>",
97
+ "platform_config": self.platform_config,
98
+ "platform_physical_key": platform_key or "<unknown>",
99
+ "detail": pool.describe(),
100
+ }
101
+ )
102
+ return out
103
+
104
+ def render_banner(self) -> str:
105
+ """The red banner text. Names every pool so the split is unmissable."""
106
+ lines = "\n".join(f"## {pool.describe()}" for pool in self.pools)
107
+ platform_key = (self.platform.physical_key if self.platform else None) or "<unknown>"
108
+ return (
109
+ "\n"
110
+ "############################################################################\n"
111
+ f"## TWO DATABASES IN ONE PROCESS — {len(self.divergent)} pool(s) do NOT point at\n"
112
+ f"## the platform database ({self.platform_config} = {platform_key}).\n"
113
+ "## There is ONE database. A pool bound elsewhere means live tables are\n"
114
+ "## being written where the platform will never see them (2026-08-09:\n"
115
+ "## matrx-scraper wrote 14 rows to a Coolify-local Postgres this way).\n"
116
+ "## FIX: resolve every pool from SUPABASE_MATRIX_* — see\n"
117
+ "## packages/matrx-scraper/matrx_scraper/db/__init__.py::register_platform_db\n"
118
+ "############################################################################\n"
119
+ f"{lines}\n"
120
+ "############################################################################"
121
+ )
122
+
123
+
124
+ async def _probe(config_name: str) -> PoolIdentity:
125
+ try:
126
+ identity = await asyncio.wait_for(
127
+ AsyncDatabaseManager.connection_identity(config_name),
128
+ timeout=PROBE_TIMEOUT_SECONDS,
129
+ )
130
+ except Exception as exc: # noqa: BLE001 — a probe never breaks boot
131
+ return PoolIdentity(config_name=config_name, error=f"{type(exc).__name__}: {exc}")
132
+ return PoolIdentity(config_name=config_name, identity=identity)
133
+
134
+
135
+ async def audit_one_database(
136
+ *,
137
+ platform_config_name: str,
138
+ sanctioned_separate: tuple[str, ...] | list[str] = (),
139
+ scream: bool = True,
140
+ ) -> OneDatabaseReport:
141
+ """Compare every registered pool's physical database against the platform's.
142
+
143
+ Asks each connection who it is (never re-reads env — env is the thing that
144
+ lied). Returns the report; screams a red banner when pools disagree or the
145
+ platform pool itself cannot be reached. Never raises.
146
+
147
+ ``sanctioned_separate`` names configs that are deliberately a different
148
+ physical database (aidream's ``cms`` product database). Anything else that
149
+ diverges is the bug.
150
+ """
151
+ from matrx_orm.core.config import get_all_database_project_names, resolve_database_name
152
+
153
+ separate = {resolve_database_name(name) for name in sanctioned_separate}
154
+ platform = resolve_database_name(platform_config_name)
155
+ names = sorted(set(get_all_database_project_names()))
156
+
157
+ probes = await asyncio.gather(*(_probe(name) for name in names))
158
+ by_name = {probe.config_name: probe for probe in probes}
159
+ platform_probe = by_name.get(platform)
160
+
161
+ platform_key = platform_probe.physical_key if platform_probe else None
162
+ divergent = [
163
+ probe
164
+ for probe in probes
165
+ if probe.config_name != platform
166
+ and probe.config_name not in separate
167
+ and probe.identity is not None
168
+ and platform_key is not None
169
+ and probe.physical_key != platform_key
170
+ ]
171
+ failures = [probe for probe in probes if probe.identity is None]
172
+
173
+ report = OneDatabaseReport(
174
+ platform_config=platform,
175
+ platform=platform_probe,
176
+ pools=list(probes),
177
+ divergent=divergent,
178
+ probe_failures=failures,
179
+ sanctioned_separate=tuple(sorted(separate)),
180
+ )
181
+
182
+ if not scream:
183
+ return report
184
+
185
+ # A pool that cannot answer is an unverified pool — say so every time, not
186
+ # only on the happy path.
187
+ for probe in failures:
188
+ vcprint(f"[one-database] pool probe failed: {probe.describe()}", color="yellow")
189
+
190
+ if divergent:
191
+ vcprint(report.render_banner(), color="red")
192
+ elif platform_probe is None or platform_probe.identity is None:
193
+ detail = platform_probe.error if platform_probe else "not registered"
194
+ vcprint(
195
+ f"[one-database] cannot verify pool agreement — the platform config "
196
+ f"{platform!r} did not answer ({detail}). The check is NOT trustworthy "
197
+ "until it does.",
198
+ color="red",
199
+ )
200
+ else:
201
+ extra = f" (+{len(separate)} declared-separate)" if separate else ""
202
+ vcprint(
203
+ f"[one-database] {len(probes) - len(separate)} pool(s) all resolve the ONE "
204
+ f"database: {platform_key}{extra}.",
205
+ color="green",
206
+ )
207
+ return report
208
+
209
+
210
+ __all__ = ["OneDatabaseReport", "PoolIdentity", "audit_one_database"]
@@ -0,0 +1,138 @@
1
+ """THE ONE DATABASE — the single place any Matrx package resolves Postgres.
2
+
3
+ **Read this before you conclude anything about "which database" a package uses.**
4
+
5
+ There is exactly ONE database per deployment. Every Matrx package — matrx-rag,
6
+ matrx-files, matrx-scraper, matrx-seo, matrx-runtime, and every future one —
7
+ puts its schema in that same Postgres. `rag.*`, `files.*`, `scraper.*`, `web.*`,
8
+ `seo.*`, `runtime.*` are schemas in ONE database, not databases.
9
+
10
+ WHERE that database lives is a DEPLOYMENT input, not a code decision. Today the
11
+ hosted product points at Supabase (project `txzxabzwovsujtloxrus`); a client who
12
+ wants to own their data points the SAME five variables at THEIR Postgres and the
13
+ entire platform moves with them. That is the whole design: one connection, set
14
+ once, per deployment.
15
+
16
+ {PLATFORM_DB_ENV_PREFIX}_HOST
17
+ {PLATFORM_DB_ENV_PREFIX}_PORT
18
+ {PLATFORM_DB_ENV_PREFIX}_DATABASE_NAME
19
+ {PLATFORM_DB_ENV_PREFIX}_USER
20
+ {PLATFORM_DB_ENV_PREFIX}_PASSWORD
21
+
22
+ **ONE connection, REQUIRED — never a chain of candidates.** A package takes
23
+ exactly one database connection, from exactly one set of variables. If resolution
24
+ fails, this module RAISES: no degraded mode, no "optional" binding, no quiet
25
+ reach for `DATABASE_URL`, a `<PKG>_DATABASE_URL`, or a `<PKG>_POSTGRES_*` set.
26
+
27
+ This does NOT mean a package may not own a database. **Every matrx-* package
28
+ ships its own schemas and migrations and can create and run them on a Postgres of
29
+ its own, standalone — that independence is a requirement, never remove it.** What
30
+ a package may not have is a SECOND CANDIDATE for where it connects.
31
+
32
+ That prohibition is not stylistic — it is the fix for a real incident. Each
33
+ package used to resolve `<PKG>_DATABASE_URL` → `DATABASE_URL` → `<PKG>_POSTGRES_*`.
34
+ Coolify injects a `DATABASE_URL` into every service automatically, so the
35
+ standalone scraper silently bound the LIVE, SHARED `scraper.*` tables to a
36
+ service-local Postgres and wrote 14 rows the platform never saw. A fallback
37
+ ladder does not add resilience; it hands the choice of database to whatever the
38
+ host happens to have set. The defect is the CHAIN, not the destination.
39
+
40
+ Guard: `scripts/check_one_database.py` fails the build if any package
41
+ reintroduces such a variable. If you are tempted to add one, you want a
42
+ different VALUE for these five variables in that deployment — not a new variable.
43
+ """
44
+
45
+ from __future__ import annotations
46
+
47
+ import os
48
+ from urllib.parse import quote
49
+
50
+ __all__ = [
51
+ "PLATFORM_DB_ENV_PREFIX",
52
+ "PLATFORM_DB_ENV_OVERRIDES",
53
+ "platform_connection_url",
54
+ "register_platform_db",
55
+ ]
56
+
57
+ # The ONE database's env prefix. Historical name — it predates the platform
58
+ # being Postgres-agnostic and says nothing about the engine. Any Postgres works.
59
+ PLATFORM_DB_ENV_PREFIX = "SUPABASE_MATRIX"
60
+
61
+ # `..._DATABASE_NAME` rather than the default `..._NAME`, because `..._NAME` is
62
+ # already used elsewhere to mean "name of a registered matrx-orm config".
63
+ PLATFORM_DB_ENV_OVERRIDES = {"NAME": "SUPABASE_MATRIX_DATABASE_NAME"}
64
+
65
+
66
+ def _missing_env_message(package: str) -> str:
67
+ who = f"{package}: " if package else ""
68
+ return (
69
+ f"{who}cannot resolve the platform database — set "
70
+ f"{PLATFORM_DB_ENV_PREFIX}_HOST/_PORT/_DATABASE_NAME/_USER/_PASSWORD. "
71
+ "These five variables ARE the database: point them at Supabase, at a "
72
+ "client's Postgres, or at a local instance. There is exactly one "
73
+ "database connection per deployment — one set of variables, required. "
74
+ "There is no second candidate to try, and adding one would let the host "
75
+ "silently decide which database this package lands in."
76
+ )
77
+
78
+
79
+ def platform_connection_url(*, package: str = "") -> str:
80
+ """Build the ONE database's connection URL for ad-hoc / migration use.
81
+
82
+ Raises ``RuntimeError`` if the five variables are not fully set. There is
83
+ deliberately no fallback — see the module docstring.
84
+ """
85
+ host = os.environ.get(f"{PLATFORM_DB_ENV_PREFIX}_HOST")
86
+ user = os.environ.get(f"{PLATFORM_DB_ENV_PREFIX}_USER")
87
+ password = os.environ.get(f"{PLATFORM_DB_ENV_PREFIX}_PASSWORD")
88
+ database = os.environ.get(PLATFORM_DB_ENV_OVERRIDES["NAME"]) or "postgres"
89
+ port = os.environ.get(f"{PLATFORM_DB_ENV_PREFIX}_PORT") or "5432"
90
+
91
+ if not (host and user and password):
92
+ raise RuntimeError(_missing_env_message(package))
93
+
94
+ return (
95
+ f"postgresql://{quote(user, safe='')}:{quote(password, safe='')}"
96
+ f"@{host}:{port}/{database}"
97
+ )
98
+
99
+
100
+ def register_platform_db(
101
+ name: str,
102
+ *,
103
+ additional_schemas: list[str],
104
+ pool_min: int = 1,
105
+ pool_max: int = 5,
106
+ ssl: object | None = None,
107
+ package: str = "",
108
+ ) -> None:
109
+ """Register matrx-orm config ``name`` against the ONE database.
110
+
111
+ ``name`` is a matrx-orm *config name* (e.g. ``"matrx_rag"``), NOT a
112
+ database — every package's models bake a config name so a host can alias it
113
+ onto an already-open pool. This function is what standalone boot calls when
114
+ no host did that.
115
+
116
+ ``ssl`` defaults to ``None``, which lets ``{PREFIX}_SSL`` decide (matrx-orm
117
+ itself defaults to ``"require"``). That is what makes a plain local or
118
+ client-owned Postgres reachable: set ``SUPABASE_MATRIX_SSL=disable``. Do not
119
+ hardcode a TLS posture here.
120
+
121
+ Idempotent. Raises ``RuntimeError`` if the environment is incomplete.
122
+ """
123
+ from matrx_orm import is_database_registered, register_database_from_env
124
+
125
+ if is_database_registered(name):
126
+ return
127
+
128
+ ok = register_database_from_env(
129
+ name=name,
130
+ env_prefix=PLATFORM_DB_ENV_PREFIX,
131
+ additional_schemas=additional_schemas,
132
+ env_var_overrides=dict(PLATFORM_DB_ENV_OVERRIDES),
133
+ pool_min=pool_min,
134
+ pool_max=pool_max,
135
+ ssl=ssl,
136
+ )
137
+ if not ok:
138
+ raise RuntimeError(_missing_env_message(package))
@@ -15,3 +15,11 @@ process identity cache.
15
15
  `True` for compatibility. The cache bypass controls only cache population; it
16
16
  does not weaken database authorization, filters, hydration, or transaction
17
17
  semantics.
18
+
19
+ ## Aggregate projections
20
+
21
+ A scalar `.aggregate(...)` projects only its aggregate expressions. It never
22
+ inherits the model query's default `*` projection. A grouped aggregate with no
23
+ explicit `.values(...)` projects the grouped columns plus its aggregate
24
+ expressions. These shapes keep every generated aggregate valid in PostgreSQL
25
+ without forcing callers to add a redundant projection.
@@ -583,6 +583,12 @@ class QueryExecutor:
583
583
  reason="group_by() entries require a column name or typed expression",
584
584
  details={"entry": repr(f)},
585
585
  )
586
+ elif aggregations:
587
+ # A scalar aggregate owns the entire projection. Keeping the
588
+ # default ``*`` would emit ``SELECT *, COUNT(...)`` and make
589
+ # Postgres require every model column in a nonexistent GROUP
590
+ # BY clause.
591
+ pass
586
592
  elif select_related or explicit_joins:
587
593
  # Need explicit table prefix to avoid ambiguity with joined columns
588
594
  select_parts.insert(0, f"{self._base_ref()}.*")
@@ -4,7 +4,7 @@ Fernet, keyed by ``CREDENTIALS_ENCRYPTION_KEY``. Every package and every
4
4
  deployment that reads or writes encrypted credentials holds the SAME key and
5
5
  uses THIS module — there is no privileged decryptor anywhere, and no second
6
6
  Fernet instance may ever be created (`aidream.services.user_secrets.encryption`
7
- and `aidream.services.scraper.credentials` delegate here).
7
+ and `aidream.services.web_credentials.credentials` delegate here).
8
8
 
9
9
  Key resolution (identical to the historical aidream implementation so every
10
10
  existing ciphertext keeps decrypting):
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "matrx-orm"
3
- version = "3.1.48"
3
+ version = "3.1.49"
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" }
@@ -307,12 +307,20 @@ class TestBaseManagerAggregate:
307
307
  # before terminating. (aggregate() on the builder also accepts kw.)
308
308
  qb = qb.annotate(total=Sum("amount"), n=Count("id"))
309
309
  sql, params = QueryExecutor(qb._build_query())._build_query()
310
+ assert "SELECT *" not in sql.split(" FROM ")[0]
310
311
  assert "SUM(amount) AS total" in sql
311
312
  assert "COUNT(id) AS n" in sql
312
313
  assert "FROM orders" in sql
313
314
  assert "WHERE status = $1" in sql
314
315
  assert params == ["paid"]
315
316
 
317
+ def test_aggregate_composite_distinct_has_no_model_projection(self):
318
+ CE = _make_chunk_entity_model()
319
+ qb = CE.filter().annotate(pairs=Count("entity_id", "chunk_id", distinct=True))
320
+ sql, _ = QueryExecutor(qb._build_query())._build_query()
321
+ select_clause = sql.split(" FROM ")[0]
322
+ assert select_clause == "SELECT COUNT(DISTINCT (entity_id, chunk_id)) AS pairs"
323
+
316
324
 
317
325
  class TestValuesDeduplicatesAnnotateAliases:
318
326
  """Regression: ``.values("col", "alias")`` after ``.annotate(alias=Count())``
@@ -0,0 +1,105 @@
1
+ """THE ONE DATABASE — pin the single resolver every Matrx package shares.
2
+
3
+ These tests fail if someone reintroduces a way for one package's schema to land
4
+ in a different Postgres than the rest of the platform.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import subprocess
10
+ import sys
11
+ from pathlib import Path
12
+
13
+ import pytest
14
+
15
+ from matrx_orm import platform_db
16
+
17
+ REPO = Path(__file__).resolve().parents[3]
18
+
19
+ # Every retired per-package connection variable. None of these may ever again
20
+ # influence where a schema lands.
21
+ RETIRED_VARS = [
22
+ "DATABASE_URL",
23
+ "RAG_DATABASE_URL",
24
+ "FILES_DATABASE_URL",
25
+ "RUNTIME_DATABASE_URL",
26
+ "SCRAPER_DATABASE_URL",
27
+ "MATRX_RAG_POSTGRES_HOST",
28
+ "MATRX_FILES_POSTGRES_HOST",
29
+ "MATRX_RUNTIME_POSTGRES_HOST",
30
+ "MATRX_SCRAPER_POSTGRES_HOST",
31
+ ]
32
+
33
+
34
+ def _clear_platform_env(monkeypatch: pytest.MonkeyPatch) -> None:
35
+ for suffix in ("HOST", "PORT", "USER", "PASSWORD", "DATABASE_NAME"):
36
+ monkeypatch.delenv(f"SUPABASE_MATRIX_{suffix}", raising=False)
37
+
38
+
39
+ def test_connection_url_ignores_every_retired_second_database_var(
40
+ monkeypatch: pytest.MonkeyPatch,
41
+ ) -> None:
42
+ """A service-local Postgres URL must NOT be able to fork a live schema."""
43
+ _clear_platform_env(monkeypatch)
44
+ for var in RETIRED_VARS:
45
+ monkeypatch.setenv(var, "postgresql://a:b@rogue-postgres.internal:5434/rogue")
46
+
47
+ with pytest.raises(RuntimeError) as exc:
48
+ platform_db.platform_connection_url(package="matrx-test")
49
+
50
+ # It raises rather than silently binding to the rogue host, and the message
51
+ # tells the reader what the ONE database actually is.
52
+ assert "rogue-postgres" not in str(exc.value)
53
+ assert "SUPABASE_MATRIX_HOST" in str(exc.value)
54
+
55
+
56
+ def test_connection_url_builds_from_the_five_platform_vars(
57
+ monkeypatch: pytest.MonkeyPatch,
58
+ ) -> None:
59
+ monkeypatch.setenv("SUPABASE_MATRIX_HOST", "db.example.com")
60
+ monkeypatch.setenv("SUPABASE_MATRIX_PORT", "6543")
61
+ monkeypatch.setenv("SUPABASE_MATRIX_USER", "svc")
62
+ monkeypatch.setenv("SUPABASE_MATRIX_PASSWORD", "p@ss/word")
63
+ monkeypatch.setenv("SUPABASE_MATRIX_DATABASE_NAME", "postgres")
64
+
65
+ url = platform_db.platform_connection_url()
66
+
67
+ # Password is URL-quoted, not naively interpolated.
68
+ assert url == "postgresql://svc:p%40ss%2Fword@db.example.com:6543/postgres"
69
+
70
+
71
+ def test_a_client_owned_postgres_is_just_different_values(
72
+ monkeypatch: pytest.MonkeyPatch,
73
+ ) -> None:
74
+ """The whole point: relocating the platform is a VALUE change, not a new var."""
75
+ monkeypatch.setenv("SUPABASE_MATRIX_HOST", "localhost")
76
+ monkeypatch.setenv("SUPABASE_MATRIX_PORT", "5432")
77
+ monkeypatch.setenv("SUPABASE_MATRIX_USER", "postgres")
78
+ monkeypatch.setenv("SUPABASE_MATRIX_PASSWORD", "postgres")
79
+ monkeypatch.setenv("SUPABASE_MATRIX_DATABASE_NAME", "matrx")
80
+
81
+ assert (
82
+ platform_db.platform_connection_url()
83
+ == "postgresql://postgres:postgres@localhost:5432/matrx"
84
+ )
85
+
86
+
87
+ def test_register_platform_db_leaves_tls_to_the_deployment() -> None:
88
+ """ssl defaults to None so SUPABASE_MATRIX_SSL governs — a local Postgres
89
+ with TLS off must remain reachable."""
90
+ import inspect
91
+
92
+ sig = inspect.signature(platform_db.register_platform_db)
93
+ assert sig.parameters["ssl"].default is None
94
+
95
+
96
+ def test_repo_guard_reports_clean() -> None:
97
+ """The build-blocking guard must pass on the current tree."""
98
+ script = REPO / "scripts" / "check_one_database.py"
99
+ if not script.exists(): # package used outside the monorepo
100
+ pytest.skip("monorepo guard script not present")
101
+
102
+ result = subprocess.run(
103
+ [sys.executable, str(script)], capture_output=True, text=True, cwd=REPO
104
+ )
105
+ assert result.returncode == 0, result.stdout + result.stderr
File without changes