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