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.
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/PKG-INFO +2 -2
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/FEATURE.md +25 -2
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/pooler_role.py +44 -27
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/pyproject.toml +2 -2
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/live_pooler_role.py +28 -1
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/test_pooler_role_guard.py +86 -4
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/.gitignore +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/AGENTS.md +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/BASE_CLASS_METHODS.md +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/CLAUDE.md +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/MODEL_API.md +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/README.md +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/database/orm/extended/managers/ai_model_base.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/docs/migrations.md +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/MODULE_README.md +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/__init__.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/adapters/MODULE_README.md +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/adapters/__init__.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/adapters/async_postgresql.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/adapters/base_adapter.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/adapters/postgresql.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/adapters/postgrest_client_adapter.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/adapters/supabase_adapter.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/admin/__init__.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/admin/router.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/api/__init__.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/api/auth.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/api/config.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/api/handlers.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/api/protocol.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/api/server.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/associations.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/cache_debug.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/catalog.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/catalog_sql.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/client/MODULE_README.md +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/client/__init__.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/client/postgres_connection.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/client/postgrest.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/client/supabase_auth.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/client/supabase_config.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/client/supabase_manager.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/EXTENDED-TASK.md +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/MODULE_README.md +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/RELATIONS-TASKS.md +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/TASKS.md +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/__init__.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/async_db_manager.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/base.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/config.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/connections.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/db_function.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/diagnostics.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/expressions.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/extended.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/fields.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/introspection.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/listen.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/loop_filters.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/model_dto.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/model_view.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/one_database.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/paginator.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/pool_maintenance.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/pool_watch.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/pydantic_bridge.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/registry.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/relations.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/resilience.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/rls_session.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/signals.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/tls.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/transaction.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/types.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/core/write_queue.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/entity.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/error_handling.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/exceptions.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/extended/__init__.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/extended/app_error_handler.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/identity_rule.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/middleware/__init__.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/middleware/base.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/migrations/MODULE_README.md +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/migrations/__init__.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/migrations/cli.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/migrations/ddl.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/migrations/diff.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/migrations/executor.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/migrations/integration.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/migrations/loader.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/migrations/operations.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/migrations/state.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/migrations/table_filter.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/operations/MODULE_README.md +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/operations/__init__.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/operations/bulk_update_values.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/operations/conflict.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/operations/conflict_writes.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/operations/create.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/operations/db_functions.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/operations/delete.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/operations/dynamic_admin.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/operations/dynamic_crud.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/operations/graph_walk.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/operations/latest_rows.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/operations/queue_claim.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/operations/read.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/operations/relational_backfill.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/operations/staging_load.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/operations/update.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/platform_db.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/py.typed +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/python_sql/MODULE_README.md +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/python_sql/__init__.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/python_sql/db_objects.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/python_sql/table_detailed_relationships.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/python_sql/table_typescript_relationship.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/query/FEATURE.md +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/query/MODULE_README.md +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/query/__init__.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/query/builder.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/query/executor.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/schema_builder/FEATURE.md +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/schema_builder/MODULE_README.md +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/schema_builder/__init__.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/schema_builder/code_handler.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/schema_builder/codegen_writer.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/schema_builder/columns.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/schema_builder/common.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/schema_builder/diff_preview.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/schema_builder/entity_capabilities.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/schema_builder/generator.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/schema_builder/helpers/__init__.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/schema_builder/helpers/base_generators.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/schema_builder/helpers/entity_generators.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/schema_builder/helpers/git_checker.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/schema_builder/package_wiring.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/schema_builder/relationships.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/schema_builder/runner.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/schema_builder/schema.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/schema_builder/schema_manager.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/schema_builder/tables.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/schema_builder/views.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/secrets_battery/FEATURE.md +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/secrets_battery/__init__.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/secrets_battery/attachments.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/secrets_battery/crypto.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/secrets_battery/items.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/secrets_battery/model.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/secrets_battery/org_service.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/secrets_battery/service.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/session/README.md +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/session/__init__.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/session/coalesce.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/session/dag.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/session/errors.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/session/fallback.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/session/flush.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/session/lifecycle.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/session/managed.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/session/op.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/session/reads.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/session/session.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/session/telemetry.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/sql_executor/MODULE_README.md +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/sql_executor/__init__.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/sql_executor/executor.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/sql_executor/queries.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/sql_executor/registry.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/sql_executor/types.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/sql_executor/utils.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/state.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/utils/__init__.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/utils/sql_utils.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/matrx_orm/utils/type_converters.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/scripts/git-branches.sh +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/scripts/publish.sh +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/scripts/release.sh +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/MODULE_README.md +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/__init__.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/conftest.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/MODULE_README.md +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/__init__.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_admin_db_columns.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_admin_search_negation.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_admin_search_sentinels.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_advisory_lock.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_agent_message.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_api_auth.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_api_config.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_api_handlers.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_api_protocol.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_array_agg_order_by.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_associations.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_bulk_hydration_offload.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_bulk_update_by_pk.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_bulk_upsert_increment_set_fields.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_cache_debug.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_call_function.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_case_when.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_catalog.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_composite_pk_filter.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_composite_pk_write_paths.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_config.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_conflict_target.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_conflict_writes.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_connection_codecs.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_connection_poison_guard.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_connection_primitives.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_count_composite_distinct.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_db_function_field.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_ddl_generator.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_derived_table.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_dirty_tracking.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_dynamic_crud.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_exceptions.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_execute_query_integrity_mapping.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_expression_primitives.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_fields.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_filter_jsonb_agg_array_index.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_from_alias.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_fts.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_func_expression_args.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_graph_walk.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_having_annotate_alias.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_insert_ignore_and_admin_primitives.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_introspect_rls_policies.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_join_and_aggregate.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_latest_rows.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_listen_notify.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_managed_write_guard.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_matrx_entity.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_migration_diff_types.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_migration_loader.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_model_instance.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_model_meta.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_optimistic_concurrency.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_order_by_expression.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_parameter_name_hydration.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_patch_jsonb_path.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_pool_holder_census.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_pool_loop_recreation.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_pool_maintenance.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_postgrest_filters.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_pydantic_bridge.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_query_builder.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_query_error_detail.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_query_executor_sql.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_query_timeout.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_querybuilder_clone_semantics.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_queue_claim.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_registry.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_registry_multi_database.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_registry_multi_schema.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_relational_backfill.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_relations.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_resilience.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_rls_session_scope.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_schema_exists.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_session_advisory_lock.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_state_cache.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_subquery_filter_raw.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_subquery_in_filter.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_supabase_auth.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_supabase_config.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_tls.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_typed_join_projection.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_typed_predicates.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_update_case_expression.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_update_subquery.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_update_with_rebase.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_upsert_default_leak.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_upsert_with_conflict.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_values_jsonb_decode.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_watched_lifecycle.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level1/test_write_retry_semantics.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level2/MODULE_README.md +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level2/__init__.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level2/conftest.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level2/test_bulk_ops.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level2/test_cache_integration.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level2/test_crud.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level2/test_foreign_keys.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level2/test_m2m.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level2/test_manager.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level2/test_migrations_live.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level2/test_query_execution.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/level2/test_schema_diff.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/sample_project/.env.example +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/sample_project/README.md +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/sample_project/__init__.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/sample_project/generate.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/sample_project/generated/.gitkeep +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/sample_project/matrx_orm.yaml +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/sample_project/test_schema_generation.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/sample_project_desktop/.env.example +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/sample_project_desktop/README.md +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/sample_project_desktop/__init__.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/sample_project_desktop/client_example.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/sample_project_desktop/client_example.ts +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/sample_project_desktop/client_supabase_example.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/sample_project_desktop/server.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/schema/entity_tests.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/schema/test_base_generation.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/schema/test_composite_pk_fk_generation.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/schema/test_composite_unique_generation.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/schema/test_dto_identity_generation.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/schema/test_entity_capability_generation.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/schema/test_generate_schema.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/schema/test_junction_analysis.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/schema/test_multi_schema_output.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/schema/test_sql_expression_defaults.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/schema/test_table_relationships_query.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/schema/test_view_generation.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/schema_builder/test_array_default_parsing.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/schema_builder/test_focused_generation_plan.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/schema_builder/test_host_models_aggregator.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/session/__init__.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/session/conftest.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/session/test_cache_no_downgrade.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/session/test_capture_poison_proof.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/session/test_coalesce.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/session/test_dag.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/session/test_disk_spill_fallback.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/session/test_flush_individual_fallback.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/session/test_governed_write.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/session/test_managed.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/session/test_op_enqueue_site.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/session/test_reads.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/session/test_session.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/test_database_name_alias.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/test_db_functions.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/test_model_cls_refactor.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/test_no_blocking_asyncpg_tls.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/test_one_database.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/test_secrets_battery.py +0 -0
- {matrx_orm-3.1.75 → matrx_orm-3.1.76}/tests/test_sql_param_casts.py +0 -0
- {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.
|
|
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.
|
|
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-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
"""
|
|
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
|
|
171
|
+
"""Scream about a directly observed borrowed role and fan out to sinks."""
|
|
170
172
|
vcprint(
|
|
171
|
-
"POOLER ROLE CONTAMINATION — an
|
|
172
|
-
"
|
|
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
|
|
176
|
+
f" pooled session was: {event.contaminated_role} (clean login role "
|
|
175
177
|
f"{event.login_role})\n"
|
|
176
|
-
f"
|
|
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]
|
|
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"
|
|
199
|
-
f"{event.
|
|
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
|
-
|
|
246
|
-
current =
|
|
247
|
-
login =
|
|
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
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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.
|
|
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.
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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=[
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|