matrx-orm 3.1.233__tar.gz → 3.1.235__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.233 → matrx_orm-3.1.235}/.gitignore +6 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/PKG-INFO +2 -2
- matrx_orm-3.1.235/matrx_orm/core/foreign_session_state.py +194 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/pytest_plugin.py +22 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/query/builder.py +15 -1
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/session/fallback.py +34 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/pyproject.toml +2 -2
- matrx_orm-3.1.235/tests/level1/test_borrowed_role_never_reaches_server_calls.py +279 -0
- matrx_orm-3.1.235/tests/session/test_tests_never_write_live_system_error.py +37 -0
- matrx_orm-3.1.235/tests/test_first_asks_the_database_for_one_row.py +48 -0
- matrx_orm-3.1.233/matrx_orm/core/foreign_session_state.py +0 -112
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/AGENTS.md +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/BASE_CLASS_METHODS.md +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/CHANGELOG.md +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/CLAUDE.md +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/MODEL_API.md +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/README.md +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/database/orm/extended/managers/ai_model_base.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/docs/migrations.md +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/FEATURE.md +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/MODULE_README.md +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/__init__.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/adapters/MODULE_README.md +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/adapters/__init__.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/adapters/async_postgresql.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/adapters/base_adapter.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/adapters/postgrest_client_adapter.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/adapters/supabase_adapter.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/admin/FEATURE.md +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/admin/__init__.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/admin/router.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/api/__init__.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/api/auth.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/api/config.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/api/handlers.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/api/protocol.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/api/server.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/associations.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/cache_debug.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/catalog.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/catalog_sql.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/client/MODULE_README.md +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/client/__init__.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/client/postgres_connection.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/client/postgrest.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/client/supabase_auth.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/client/supabase_config.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/client/supabase_manager.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/core/EXTENDED-TASK.md +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/core/FEATURE.md +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/core/MODULE_README.md +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/core/RELATIONS-TASKS.md +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/core/TASKS.md +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/core/__init__.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/core/admin_lane.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/core/async_db_manager.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/core/base.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/core/config.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/core/connections.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/core/db_function.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/core/diagnostics.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/core/expressions.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/core/extended.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/core/fields.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/core/introspection.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/core/listen.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/core/loop_filters.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/core/model_dto.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/core/model_view.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/core/one_database.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/core/paginator.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/core/pool_maintenance.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/core/pool_watch.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/core/pooler_role.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/core/pydantic_bridge.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/core/read_write.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/core/registry.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/core/relations.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/core/resilience.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/core/rls_session.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/core/session_context.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/core/signals.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/core/statement_ceiling.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/core/tls.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/core/transaction.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/core/types.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/core/write_queue.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/core/write_retry.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/entity.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/error_handling.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/exceptions.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/extended/__init__.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/extended/app_error_handler.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/identity_rule.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/local_sqlite.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/middleware/__init__.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/middleware/base.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/migrations/MODULE_README.md +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/migrations/__init__.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/migrations/cli.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/migrations/ddl.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/migrations/diff.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/migrations/executor.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/migrations/integration.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/migrations/loader.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/migrations/operations.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/migrations/state.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/migrations/table_filter.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/no_org_assignment.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/operations/FEATURE.md +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/operations/MODULE_README.md +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/operations/__init__.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/operations/bulk_update_values.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/operations/catalog_select.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/operations/conflict.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/operations/conflict_writes.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/operations/create.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/operations/db_functions.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/operations/delete.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/operations/dynamic_admin.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/operations/dynamic_crud.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/operations/graph_walk.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/operations/latest_rows.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/operations/queue_claim.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/operations/read.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/operations/relational_backfill.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/operations/staging_load.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/operations/update.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/platform_access.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/platform_db.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/py.typed +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/pytest_lock_guard.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/python_sql/MODULE_README.md +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/python_sql/__init__.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/python_sql/db_objects.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/python_sql/table_detailed_relationships.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/python_sql/table_typescript_relationship.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/query/FEATURE.md +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/query/MODULE_README.md +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/query/__init__.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/query/executor.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/resources/__init__.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/resources/additional-assigners-2026-09-12.json +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/resources/org_assignment_trigger_baseline.json +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/schema_builder/FEATURE.md +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/schema_builder/MODULE_README.md +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/schema_builder/__init__.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/schema_builder/code_handler.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/schema_builder/codegen_writer.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/schema_builder/columns.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/schema_builder/common.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/schema_builder/diff_preview.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/schema_builder/entity_capabilities.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/schema_builder/excluded_targets.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/schema_builder/generator.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/schema_builder/helpers/__init__.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/schema_builder/helpers/base_generators.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/schema_builder/helpers/entity_generators.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/schema_builder/helpers/git_checker.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/schema_builder/package_wiring.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/schema_builder/relationships.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/schema_builder/runner.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/schema_builder/schema.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/schema_builder/schema_manager.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/schema_builder/tables.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/schema_builder/views.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/secrets_battery/FEATURE.md +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/secrets_battery/__init__.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/secrets_battery/attachments.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/secrets_battery/backup_restore.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/secrets_battery/crypto.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/secrets_battery/host_authorization.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/secrets_battery/items.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/secrets_battery/model.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/secrets_battery/mutation_receipts.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/secrets_battery/native_enable.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/secrets_battery/native_lifecycle.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/secrets_battery/native_passkeys.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/secrets_battery/native_source.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/secrets_battery/org_service.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/secrets_battery/org_vault_gate.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/secrets_battery/service.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/secrets_battery/vault_history.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/session/README.md +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/session/__init__.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/session/coalesce.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/session/dag.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/session/errors.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/session/flush.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/session/lifecycle.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/session/managed.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/session/op.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/session/reads.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/session/session.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/session/telemetry.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/sql_executor/MODULE_README.md +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/sql_executor/__init__.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/sql_executor/executor.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/sql_executor/queries.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/sql_executor/registry.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/sql_executor/types.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/sql_executor/utils.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/state.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/utils/__init__.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/utils/sql_utils.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/utils/type_converters.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/matrx_orm/write_actor.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/scripts/git-branches.sh +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/scripts/publish.sh +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/scripts/release.sh +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/MODULE_README.md +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/__init__.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/conftest.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/MODULE_README.md +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/__init__.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_adapter_registry.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_admin_db_columns.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_admin_response_rendering.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_admin_search_negation.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_admin_search_sentinels.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_advisory_lock.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_agent_message.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_api_auth.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_api_config.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_api_handlers.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_api_protocol.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_array_agg_order_by.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_association_trigger_actor_naming.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_associations.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_associations_wiring.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_bulk_hydration_offload.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_bulk_update_by_pk.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_bulk_upsert_increment_set_fields.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_cache_debug.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_call_function.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_case_when.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_catalog.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_column_scoped_writes.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_committed_row_guard.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_composite_pk_filter.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_composite_pk_write_paths.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_config.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_conflict_target.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_conflict_writes.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_connection_codecs.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_connection_poison_guard.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_connection_primitives.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_count_composite_distinct.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_count_limit_param_leak.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_db_function_field.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_ddl_generator.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_deadlock_retry.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_derived_table.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_dirty_tracking.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_dynamic_crud.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_exceptions.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_execute_query_integrity_mapping.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_expression_primitives.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_fields.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_filter_jsonb_agg_array_index.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_fk_cycle_resolution.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_from_alias.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_fts.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_func_expression_args.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_graph_walk.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_having_annotate_alias.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_insert_ignore_and_admin_primitives.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_introspect_rls_policies.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_join_and_aggregate.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_latest_rows.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_listen_notify.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_local_sqlite.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_lock_contention_retry.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_managed_write_guard.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_matrx_entity.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_migration_diff_types.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_migration_loader.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_model_instance.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_model_meta.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_optimistic_concurrency.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_order_by_expression.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_org_assignment_inventory.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_orm_banners_are_client_safe.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_parameter_name_hydration.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_patch_jsonb_path.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_percentile_cont.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_persisted_count.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_person_scoped_session_bypasses_shared_cache.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_pool_create_never_wedges.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_pool_holder_census.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_pool_loop_recreation.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_pool_maintenance.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_postgrest_filters.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_pydantic_bridge.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_query_builder.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_query_error_detail.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_query_executor_sql.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_query_timeout.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_querybuilder_clone_semantics.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_queue_claim.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_read_connection_retry.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_read_write_declared.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_registry.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_registry_multi_database.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_registry_multi_schema.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_relational_backfill.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_relations.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_resilience.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_rls_session_scope.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_schema_exists.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_session_advisory_lock.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_standalone_write_session_context.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_state_cache.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_subquery_filter_raw.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_subquery_in_filter.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_subquery_nested_expressions.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_supabase_auth.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_supabase_config.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_tls.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_transaction_database_identity.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_transaction_gucs_one_round_trip.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_transaction_pool_release_sends_nothing.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_typed_join_projection.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_typed_predicates.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_update_case_expression.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_update_subquery.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_update_with_rebase.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_upsert_default_leak.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_upsert_with_conflict.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_values_jsonb_decode.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_watched_lifecycle.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_write_queue_deadline.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level1/test_write_retry_semantics.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level2/MODULE_README.md +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level2/__init__.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level2/conftest.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level2/test_bulk_ops.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level2/test_cache_integration.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level2/test_crud.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level2/test_foreign_keys.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level2/test_m2m.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level2/test_manager.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level2/test_migrations_live.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level2/test_query_execution.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level2/test_schema_diff.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/level2/verify_committed_row_guard_pg17.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/live_pooler_role.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/native_source_cases.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/native_source_oracle.rs +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/sample_project/.env.example +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/sample_project/README.md +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/sample_project/__init__.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/sample_project/generate.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/sample_project/generated/.gitkeep +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/sample_project/matrx_orm.yaml +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/sample_project/test_schema_generation.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/sample_project_desktop/.env.example +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/sample_project_desktop/README.md +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/sample_project_desktop/__init__.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/sample_project_desktop/client_example.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/sample_project_desktop/client_example.ts +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/sample_project_desktop/client_supabase_example.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/sample_project_desktop/server.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/schema/entity_tests.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/schema/test_base_generation.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/schema/test_composite_pk_fk_generation.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/schema/test_composite_unique_generation.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/schema/test_dto_identity_generation.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/schema/test_entity_capability_generation.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/schema/test_enum_ordering.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/schema/test_excluded_fk_targets.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/schema/test_generate_schema.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/schema/test_junction_analysis.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/schema/test_multi_schema_output.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/schema/test_schema_analysis.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/schema/test_sql_expression_defaults.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/schema/test_table_relationships_query.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/schema/test_view_generation.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/schema_builder/test_array_default_parsing.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/schema_builder/test_enum_array_column.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/schema_builder/test_focused_generation_plan.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/schema_builder/test_host_models_aggregator.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/schema_builder/test_never_filter_columns.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/session/__init__.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/session/conftest.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/session/test_a_failed_capture_still_names_the_failure.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/session/test_cache_no_downgrade.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/session/test_capture_poison_proof.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/session/test_coalesce.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/session/test_dag.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/session/test_disk_spill_fallback.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/session/test_flush_individual_fallback.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/session/test_governed_write.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/session/test_managed.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/session/test_op_enqueue_site.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/session/test_reads.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/session/test_record_error_bounded_insert.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/session/test_record_error_storm.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/session/test_session.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/session/test_spill_guard_plugin.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/test_a_backend_handed_over_mid_transaction_never_takes_our_writes.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/test_a_bulk_cache_never_stalls_the_loop.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/test_a_keyword_column_refuses_generation.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/test_a_list_of_uuids_binds_as_uuid_array.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/test_a_read_only_backend_never_refuses_our_writes.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/test_a_sealed_field_shows_no_character_of_its_value.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/test_array_arg_element_type.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/test_attachment_mutation_serialization.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/test_backup_alias_conflict.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/test_backup_history_restore.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/test_backup_restore_live.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/test_call_function_as.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/test_call_function_none_mode.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/test_composite_fk_is_not_a_column_fk.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/test_database_name_alias.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/test_db_functions.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/test_dsn_password_masking.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/test_dynamic_select_unregistered.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/test_foreign_session_state.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/test_immutable_insert_or_exact_verify.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/test_keyed_digest.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/test_model_cls_refactor.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/test_native_enable.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/test_native_lifecycle.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/test_native_lifecycle_fences.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/test_native_passkey_receipts.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/test_native_source.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/test_no_blocking_asyncpg_tls.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/test_one_database.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/test_order_by_desc_nulls_last.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/test_org_vault_gate.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/test_package_wiring_views.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/test_pooler_role_guard.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/test_secret_audit_names_its_organization.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/test_secrets_battery.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/test_secrets_minting_keys.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/test_shared_lock_guard.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/test_sql_param_casts.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/test_state_cache_sweeps_stale.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/test_statement_ceiling_warning.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/test_sync_twins_guard.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/test_update_result_truthiness.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/test_user_visible_lookup.py +0 -0
- {matrx_orm-3.1.233 → matrx_orm-3.1.235}/tests/test_vault_aggregate_recovery_stateful.py +0 -0
|
@@ -349,3 +349,9 @@ apps/shared/.kit-*-witness.*/
|
|
|
349
349
|
.matrx/release-repeats.json.tmp
|
|
350
350
|
.matrx/fixer-ledger.json
|
|
351
351
|
/.config-manifest.json
|
|
352
|
+
|
|
353
|
+
# Agents' scratch "proof" copies of shared packages — never tracked (they break the npm planner and the frozen lockfile)
|
|
354
|
+
apps/shared/zz-proof-*/
|
|
355
|
+
|
|
356
|
+
# tsup writes this temp file while it builds a config; never commit it
|
|
357
|
+
tsup.config.bundled_*.mjs
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: matrx-orm
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.235
|
|
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.77
|
|
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
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"""Session state another client left on a shared server connection.
|
|
2
|
+
|
|
3
|
+
WHY THIS EXISTS. We reach Postgres through Supavisor in TRANSACTION mode: every
|
|
4
|
+
client shares the same pool of server connections, and Supavisor does not reset
|
|
5
|
+
session state between clients. ``pg_dump`` (17.x) begins every run with a
|
|
6
|
+
SESSION-level ``SET restrict_nonsystem_relation_kind = 'view, foreign-table'``.
|
|
7
|
+
A dump pointed at the transaction pooler (port 6543) leaves that setting on
|
|
8
|
+
whichever server connection the ``SET`` landed on, and every later statement of
|
|
9
|
+
ours that touches a view through that connection is refused at parse time::
|
|
10
|
+
|
|
11
|
+
55000 ObjectNotInPrerequisiteStateError:
|
|
12
|
+
access to non-system view "organization_member" is restricted
|
|
13
|
+
|
|
14
|
+
Measured 2026-10-05 05:49-09:04 UTC: ~50 platform errors (SEO runs list, mandate
|
|
15
|
+
resolution, membership checks, file downloads), each pinned to a few poisoned
|
|
16
|
+
backends that kept failing for minutes.
|
|
17
|
+
|
|
18
|
+
THE DEFENCE (two halves, both here so they cannot drift apart):
|
|
19
|
+
|
|
20
|
+
1. Every ORM transaction clears the setting transaction-locally at BEGIN
|
|
21
|
+
(:data:`CLEARED_GUCS`, merged into ``transaction._opening_statement_gucs``).
|
|
22
|
+
It rides the one opening statement, so it costs nothing.
|
|
23
|
+
2. A statement OUTSIDE a transaction cannot carry a ``SET LOCAL`` in the same
|
|
24
|
+
round trip. When one is refused with exactly this parse-time error, it is
|
|
25
|
+
re-run ONCE inside a transaction that clears the setting first. The refusal
|
|
26
|
+
happens during parse/rewrite, before anything executes, so the re-run is
|
|
27
|
+
side-effect free. The cause is logged as a WARNING once per connection so it
|
|
28
|
+
never hides.
|
|
29
|
+
|
|
30
|
+
A BORROWED ROLE IS THE SAME CLASS (2026-10-18). Another client of the tenant ran
|
|
31
|
+
``set_config('role', 'authenticated', false)`` through the transaction pooler (236
|
|
32
|
+
times, per pg_stat_statements) — session scope, so the server connection went back to
|
|
33
|
+
the shared pool still wearing ``authenticated``. Every server statement routed to it
|
|
34
|
+
ran as a person: ~100 ``permission denied for function claim_*`` /
|
|
35
|
+
``record_changes_drain`` / ``resolve_full_context`` errors across every worker in three
|
|
36
|
+
bursts. Same two halves:
|
|
37
|
+
|
|
38
|
+
1. Every ORM transaction opens as OUR login role: ``role = 'none'`` rides the opening
|
|
39
|
+
statement (``set_config('role', 'none', true)`` = ``SET LOCAL ROLE NONE``).
|
|
40
|
+
``rls_session`` still replaces it with the person's role, and that is LOCAL too.
|
|
41
|
+
2. A statement outside a transaction refused with 42501 (insufficient privilege) is
|
|
42
|
+
re-run ONCE inside a transaction that resets the role first. A privilege refusal
|
|
43
|
+
rolls the whole single-statement transaction back, so the re-run is side-effect
|
|
44
|
+
free. A borrowed role it observes is logged as an ERROR and recorded durably
|
|
45
|
+
(``pooler_role_contamination``); a genuine denial fails the re-run and propagates.
|
|
46
|
+
|
|
47
|
+
A checkout-time assertion cannot work on this path: in transaction mode each statement
|
|
48
|
+
may land on a different server connection, so the only place a role can be asserted is
|
|
49
|
+
inside the transaction (or single statement) that uses it — which is exactly here.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
from __future__ import annotations
|
|
53
|
+
|
|
54
|
+
import logging
|
|
55
|
+
import weakref
|
|
56
|
+
from typing import Any, Awaitable, Callable, TypeVar
|
|
57
|
+
|
|
58
|
+
logger = logging.getLogger(__name__)
|
|
59
|
+
|
|
60
|
+
RESTRICT_GUC = "restrict_nonsystem_relation_kind"
|
|
61
|
+
|
|
62
|
+
# Merged into every top-level transaction's opening GUCs. '' = no restriction
|
|
63
|
+
# (the server default); set_config(..., true) scopes it to the transaction.
|
|
64
|
+
# ``role`` = 'none' resets a borrowed role to the session's login role for the
|
|
65
|
+
# transaction (``transaction._opening_statement_gucs`` always emits ``role`` last, and
|
|
66
|
+
# an opener's own ``role`` — rls_session — replaces it).
|
|
67
|
+
CLEARED_GUCS: dict[str, str] = {RESTRICT_GUC: "", "role": "none"}
|
|
68
|
+
|
|
69
|
+
_OBJECT_NOT_IN_PREREQUISITE_STATE = "55000"
|
|
70
|
+
_INSUFFICIENT_PRIVILEGE = "42501"
|
|
71
|
+
|
|
72
|
+
T = TypeVar("T")
|
|
73
|
+
|
|
74
|
+
_warned: "weakref.WeakSet[Any]" = weakref.WeakSet()
|
|
75
|
+
_warned_ids: set[int] = set()
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def is_foreign_restrict_refusal(exc: BaseException) -> bool:
|
|
79
|
+
"""True iff ``exc`` is the parse-time refusal caused by a leaked restrict GUC."""
|
|
80
|
+
if getattr(exc, "sqlstate", None) != _OBJECT_NOT_IN_PREREQUISITE_STATE:
|
|
81
|
+
return False
|
|
82
|
+
text = str(exc)
|
|
83
|
+
return "access to non-system" in text and "is restricted" in text
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def is_privilege_refusal(exc: BaseException) -> bool:
|
|
87
|
+
"""True iff ``exc`` is a 42501 — possibly a borrowed role on a shared connection."""
|
|
88
|
+
return getattr(exc, "sqlstate", None) == _INSUFFICIENT_PRIVILEGE
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _warn_once(conn: Any, exc: BaseException, what: str) -> None:
|
|
92
|
+
try:
|
|
93
|
+
if conn in _warned:
|
|
94
|
+
return
|
|
95
|
+
_warned.add(conn)
|
|
96
|
+
except TypeError: # not weak-referenceable
|
|
97
|
+
if id(conn) in _warned_ids:
|
|
98
|
+
return
|
|
99
|
+
_warned_ids.add(id(conn))
|
|
100
|
+
logger.warning(
|
|
101
|
+
"[foreign-session-state] %s was refused because the shared server connection "
|
|
102
|
+
"carries %s from another client (a pg_dump through the transaction pooler sets it "
|
|
103
|
+
"session-wide and Supavisor does not reset it): %s — re-running once in a "
|
|
104
|
+
"transaction that clears it",
|
|
105
|
+
what,
|
|
106
|
+
RESTRICT_GUC,
|
|
107
|
+
exc,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
async def clear_in_transaction(conn: Any) -> None:
|
|
112
|
+
"""Clear the restrict GUC and any borrowed role for the current transaction on ``conn``."""
|
|
113
|
+
await conn.execute(
|
|
114
|
+
"SELECT set_config($1, $2, true), set_config($3, $4, true)",
|
|
115
|
+
RESTRICT_GUC,
|
|
116
|
+
"",
|
|
117
|
+
"role",
|
|
118
|
+
"none",
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
async def _report_borrowed_role(borrowed: str, login: str, what: str) -> None:
|
|
123
|
+
logger.error(
|
|
124
|
+
"[foreign-session-state] %s ran on a shared server connection wearing role '%s' "
|
|
125
|
+
"(login role '%s') left by another pooler client's session-scoped role change — "
|
|
126
|
+
"re-ran it as '%s' in a transaction that reset the role",
|
|
127
|
+
what,
|
|
128
|
+
borrowed,
|
|
129
|
+
login,
|
|
130
|
+
login,
|
|
131
|
+
)
|
|
132
|
+
try:
|
|
133
|
+
from matrx_orm.session.fallback import record_error
|
|
134
|
+
|
|
135
|
+
await record_error(
|
|
136
|
+
RuntimeError(
|
|
137
|
+
f"pooled server connection carried borrowed role '{borrowed}' "
|
|
138
|
+
f"(login '{login}'); {what} re-ran as '{login}'"
|
|
139
|
+
),
|
|
140
|
+
kind="pooler_role_contamination",
|
|
141
|
+
source_app="matrx-orm",
|
|
142
|
+
source_feature="system",
|
|
143
|
+
route="orm:foreign_session_state",
|
|
144
|
+
payload={"borrowed_role": borrowed, "login_role": login, "what": what[:500]},
|
|
145
|
+
)
|
|
146
|
+
except Exception: # noqa: BLE001 — the alarm must never cost the healed call
|
|
147
|
+
logger.exception("[foreign-session-state] could not durably record the borrowed role")
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
async def run_clearing_foreign_restrict(
|
|
151
|
+
conn: Any, run: Callable[[], Awaitable[T]], *, what: str = "statement"
|
|
152
|
+
) -> T:
|
|
153
|
+
"""Run ``run()`` (one statement on ``conn``, outside any transaction).
|
|
154
|
+
|
|
155
|
+
On the leaked-restrict refusal, or a privilege refusal (a borrowed role), run it
|
|
156
|
+
once more inside a transaction on the same client connection that clears the
|
|
157
|
+
restrict setting and resets the role first. Any other error, or a second
|
|
158
|
+
refusal, propagates unchanged.
|
|
159
|
+
"""
|
|
160
|
+
try:
|
|
161
|
+
return await run()
|
|
162
|
+
except Exception as exc:
|
|
163
|
+
privilege = is_privilege_refusal(exc)
|
|
164
|
+
if not privilege and not is_foreign_restrict_refusal(exc):
|
|
165
|
+
raise
|
|
166
|
+
if not privilege:
|
|
167
|
+
_warn_once(conn, exc, what)
|
|
168
|
+
if not privilege:
|
|
169
|
+
async with conn.transaction():
|
|
170
|
+
await clear_in_transaction(conn)
|
|
171
|
+
return await run()
|
|
172
|
+
async with conn.transaction():
|
|
173
|
+
# The transaction pins ONE server connection: read the role it carries, then
|
|
174
|
+
# reset it — both before the re-run, on that same backend.
|
|
175
|
+
row = await conn.fetchrow(
|
|
176
|
+
"SELECT current_user::text AS borrowed, session_user::text AS login"
|
|
177
|
+
)
|
|
178
|
+
await clear_in_transaction(conn)
|
|
179
|
+
result = await run()
|
|
180
|
+
borrowed = row["borrowed"] if row is not None else None
|
|
181
|
+
login = row["login"] if row is not None else None
|
|
182
|
+
if borrowed and login and borrowed != login:
|
|
183
|
+
await _report_borrowed_role(borrowed, login, what)
|
|
184
|
+
return result
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
__all__ = [
|
|
188
|
+
"CLEARED_GUCS",
|
|
189
|
+
"RESTRICT_GUC",
|
|
190
|
+
"clear_in_transaction",
|
|
191
|
+
"is_foreign_restrict_refusal",
|
|
192
|
+
"is_privilege_refusal",
|
|
193
|
+
"run_clearing_foreign_restrict",
|
|
194
|
+
]
|
|
@@ -198,3 +198,25 @@ def pytest_unconfigure(config: pytest.Config) -> None: # noqa: ARG001
|
|
|
198
198
|
_SESSION_SPILL_DIR = None
|
|
199
199
|
_PREVIOUS_ENV = None
|
|
200
200
|
_HIJACKS.clear()
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
@pytest.fixture(autouse=True)
|
|
204
|
+
def _no_live_system_error_rows():
|
|
205
|
+
"""Under pytest ``record_error`` never inserts into the live ``ops.system_error``
|
|
206
|
+
(see ``fallback.CAPTURED_UNDER_TEST``); start every test with an empty capture."""
|
|
207
|
+
try:
|
|
208
|
+
from matrx_orm.session import fallback
|
|
209
|
+
except Exception: # noqa: BLE001 — the ORM is not importable in every suite
|
|
210
|
+
yield
|
|
211
|
+
return
|
|
212
|
+
fallback.CAPTURED_UNDER_TEST.clear()
|
|
213
|
+
yield
|
|
214
|
+
fallback.CAPTURED_UNDER_TEST.clear()
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
@pytest.fixture
|
|
218
|
+
def captured_system_errors() -> list:
|
|
219
|
+
"""The ``record_error`` calls this test made, withheld from the live database."""
|
|
220
|
+
from matrx_orm.session import fallback
|
|
221
|
+
|
|
222
|
+
return fallback.CAPTURED_UNDER_TEST
|
|
@@ -48,6 +48,15 @@ async def _evict_updated_rows(model_cls: type, rows: list[Any]) -> None:
|
|
|
48
48
|
await StateManager.clear_cache(model_cls)
|
|
49
49
|
|
|
50
50
|
|
|
51
|
+
def _has_pending_in_stack(model_cls: type) -> bool:
|
|
52
|
+
"""True when any Session on the stack holds unflushed writes for this model."""
|
|
53
|
+
try:
|
|
54
|
+
from matrx_orm.session import has_pending_in_stack
|
|
55
|
+
except ImportError:
|
|
56
|
+
return False
|
|
57
|
+
return has_pending_in_stack(model_cls)
|
|
58
|
+
|
|
59
|
+
|
|
51
60
|
def _merge_pending_into_models(model_cls: type, db_models: list, filters: dict[str, Any]) -> list:
|
|
52
61
|
"""Apply pending Session state to a list of Model instances from DB.
|
|
53
62
|
|
|
@@ -945,9 +954,14 @@ class QueryBuilder(Generic[ModelT]):
|
|
|
945
954
|
async def first(self) -> ModelT | None:
|
|
946
955
|
"""Get the first matching record.
|
|
947
956
|
|
|
948
|
-
Session-aware via the same merge pathway as :meth:`all`.
|
|
957
|
+
Session-aware via the same merge pathway as :meth:`all`. With no pending
|
|
958
|
+
Session writes for this model the database is asked for one row
|
|
959
|
+
(``LIMIT 1``); fetching every row cost the DB host recorder a 50 MB,
|
|
960
|
+
780 ms read of its whole history every minute (2026-10-17)."""
|
|
949
961
|
try:
|
|
950
962
|
executor = await self._get_executor_async()
|
|
963
|
+
if not _has_pending_in_stack(self.model):
|
|
964
|
+
return await executor.first()
|
|
951
965
|
results = await executor.all()
|
|
952
966
|
merged = _merge_pending_into_models(
|
|
953
967
|
self.model,
|
|
@@ -651,6 +651,14 @@ async def _bulk_insert_failures(
|
|
|
651
651
|
return failed
|
|
652
652
|
|
|
653
653
|
|
|
654
|
+
#: Every ``record_error`` call made under pytest whose real insert was withheld.
|
|
655
|
+
CAPTURED_UNDER_TEST: list[dict[str, Any]] = []
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
def under_pytest() -> bool:
|
|
659
|
+
return "PYTEST_CURRENT_TEST" in os.environ
|
|
660
|
+
|
|
661
|
+
|
|
654
662
|
async def record_error(
|
|
655
663
|
exc: BaseException,
|
|
656
664
|
*,
|
|
@@ -756,6 +764,29 @@ async def record_error(
|
|
|
756
764
|
},
|
|
757
765
|
}
|
|
758
766
|
|
|
767
|
+
if _insert_error is _LIVE_INSERT_ERROR and under_pytest():
|
|
768
|
+
# THE LIVE ERROR LOG IS NOT A TEST FIXTURE (2026-10-17). Unit tests with fake
|
|
769
|
+
# cleaners filed 14 real ``rag_page_clean_degraded`` rows into ops.system_error
|
|
770
|
+
# through this door. Under pytest the real insert never runs: the call is kept
|
|
771
|
+
# in ``CAPTURED_UNDER_TEST`` (the ``captured_system_errors`` fixture reads it).
|
|
772
|
+
# A test that patches ``_insert_error`` itself is isolated by its own double and
|
|
773
|
+
# still reaches it; ``_insert_error`` called directly still takes its own adapter.
|
|
774
|
+
CAPTURED_UNDER_TEST.append(
|
|
775
|
+
{
|
|
776
|
+
"kind": kind,
|
|
777
|
+
"error_type": resolved_error_type,
|
|
778
|
+
"error_text": resolved_error_text,
|
|
779
|
+
"user_id": user_id,
|
|
780
|
+
"organization_id": organization_id,
|
|
781
|
+
"source_app": source_app,
|
|
782
|
+
"source_feature": source_feature,
|
|
783
|
+
"route": route,
|
|
784
|
+
"context": context,
|
|
785
|
+
"payload": payload,
|
|
786
|
+
}
|
|
787
|
+
)
|
|
788
|
+
return
|
|
789
|
+
|
|
759
790
|
try:
|
|
760
791
|
await _insert_error(
|
|
761
792
|
kind=kind,
|
|
@@ -915,6 +946,9 @@ async def _insert_error(
|
|
|
915
946
|
)
|
|
916
947
|
|
|
917
948
|
|
|
949
|
+
|
|
950
|
+
_LIVE_INSERT_ERROR = _insert_error
|
|
951
|
+
|
|
918
952
|
async def _execute_error_insert(
|
|
919
953
|
conn: Any,
|
|
920
954
|
*,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "matrx-orm"
|
|
3
|
-
version = "3.1.
|
|
3
|
+
version = "3.1.235"
|
|
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.77",
|
|
45
45
|
"pyyaml>=6.0.3",
|
|
46
46
|
"python-dotenv>=1.0",
|
|
47
47
|
"cryptography>=43.0",
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"""A pooled server connection left wearing a person's role never runs our server calls.
|
|
2
|
+
|
|
3
|
+
THE LIVE DEFECT (2026-10-18, bursts 04:58, 05:54, 07:49 UTC). ~100 ops.system_error rows
|
|
4
|
+
across every worker at once — sms_dispatcher, notification_dispatcher, record-changes
|
|
5
|
+
drain, workflow lease reaper, request audit, and an Agent run (``call_function
|
|
6
|
+
resolve_full_context`` via ``run_clearing_foreign_restrict``) — all
|
|
7
|
+
``permission denied for function ...`` on functions whose EXECUTE only postgres /
|
|
8
|
+
service_role hold. pg_stat_statements named the source: a client of the same tenant ran
|
|
9
|
+
|
|
10
|
+
select set_config('request.jwt.claims', ..., false), ...,
|
|
11
|
+
set_config('role', 'authenticated', false)
|
|
12
|
+
|
|
13
|
+
236 times through the TRANSACTION pooler. ``false`` = session scope: Supavisor hands that
|
|
14
|
+
server connection back to the shared pool still wearing ``authenticated``, and our next
|
|
15
|
+
statement routed to it runs as a person.
|
|
16
|
+
|
|
17
|
+
THE CLASS: any server-role statement (a statement outside a transaction, or a top-level
|
|
18
|
+
transaction without ``rls_session``) inherits whatever role the shared backend carries.
|
|
19
|
+
The ORM's own scopes are transaction-local and cannot leak (proved below too); the
|
|
20
|
+
defence must hold against a backend another client dirtied.
|
|
21
|
+
|
|
22
|
+
``ServerBackend`` models one Supavisor server connection with real Postgres semantics:
|
|
23
|
+
session-scope vs transaction-local ``role``, ``none`` = the login role, a server-only
|
|
24
|
+
function that refuses every role but ``postgres``.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import asyncio
|
|
30
|
+
import re
|
|
31
|
+
from contextlib import asynccontextmanager
|
|
32
|
+
from typing import Any
|
|
33
|
+
|
|
34
|
+
import asyncpg
|
|
35
|
+
import pytest
|
|
36
|
+
|
|
37
|
+
from matrx_orm.adapters import AdapterRegistry
|
|
38
|
+
from matrx_orm.core import db_function
|
|
39
|
+
from matrx_orm.core import foreign_session_state as fss
|
|
40
|
+
from matrx_orm.core.async_db_manager import AsyncDatabaseManager
|
|
41
|
+
from matrx_orm.core.rls_session import rls_session
|
|
42
|
+
from matrx_orm.core.session_context import configure_session_context
|
|
43
|
+
from matrx_orm.core.transaction import _opening_statement_gucs, transaction
|
|
44
|
+
|
|
45
|
+
LOGIN = "postgres"
|
|
46
|
+
SERVER_ONLY = "claim_pending_sms_outbound_attempts"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _denied(fn: str) -> Exception:
|
|
50
|
+
exc = asyncpg.exceptions.InsufficientPrivilegeError(f"permission denied for function {fn}")
|
|
51
|
+
exc.sqlstate = "42501"
|
|
52
|
+
return exc
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class ServerBackend:
|
|
56
|
+
"""One shared pooler server connection, as Postgres would behave."""
|
|
57
|
+
|
|
58
|
+
def __init__(self) -> None:
|
|
59
|
+
self.session_role: str | None = None # None = login role
|
|
60
|
+
self.local_role: str | None = None
|
|
61
|
+
self.in_tx = False
|
|
62
|
+
self.server_calls = 0
|
|
63
|
+
self.denials = 0
|
|
64
|
+
self.statements: list[str] = []
|
|
65
|
+
|
|
66
|
+
# --- Postgres semantics -------------------------------------------------
|
|
67
|
+
@property
|
|
68
|
+
def current_user(self) -> str:
|
|
69
|
+
role = self.local_role if self.local_role is not None else self.session_role
|
|
70
|
+
return LOGIN if role in (None, "none") else role
|
|
71
|
+
|
|
72
|
+
def _apply_set_config(self, sql: str, args: tuple[Any, ...]) -> None:
|
|
73
|
+
pairs: list[tuple[str, str, bool]] = []
|
|
74
|
+
# bound pairs: set_config($n, $m, true)
|
|
75
|
+
bound = re.findall(r"set_config\(\$(\d+),\s*\$(\d+),\s*(true|false)\)", sql)
|
|
76
|
+
for name_i, value_i, local in bound:
|
|
77
|
+
pairs.append((args[int(name_i) - 1], args[int(value_i) - 1], local == "true"))
|
|
78
|
+
literal = re.findall(r"set_config\('([^']+)',\s*'([^']*)',\s*(true|false)\)", sql)
|
|
79
|
+
for name, value, local in literal:
|
|
80
|
+
pairs.append((name, value, local == "true"))
|
|
81
|
+
for name, value, is_local in pairs:
|
|
82
|
+
if name != "role":
|
|
83
|
+
continue
|
|
84
|
+
if is_local:
|
|
85
|
+
if self.in_tx: # outside a transaction a LOCAL set dies with the statement
|
|
86
|
+
self.local_role = value
|
|
87
|
+
else:
|
|
88
|
+
self.session_role = value
|
|
89
|
+
|
|
90
|
+
def _end_tx(self) -> None:
|
|
91
|
+
self.in_tx = False
|
|
92
|
+
self.local_role = None
|
|
93
|
+
|
|
94
|
+
async def execute(self, sql: str, *args: Any, timeout: Any = None) -> str: # noqa: ASYNC109 — mirrors asyncpg's connection signature
|
|
95
|
+
self.statements.append(sql)
|
|
96
|
+
s = sql.strip().upper()
|
|
97
|
+
if s == "BEGIN":
|
|
98
|
+
self.in_tx = True
|
|
99
|
+
elif s in ("COMMIT", "ROLLBACK"):
|
|
100
|
+
self._end_tx()
|
|
101
|
+
elif "SET_CONFIG" in s:
|
|
102
|
+
self._apply_set_config(sql, args)
|
|
103
|
+
elif SERVER_ONLY in sql:
|
|
104
|
+
self._server_only()
|
|
105
|
+
return "OK"
|
|
106
|
+
|
|
107
|
+
def _server_only(self) -> list[dict[str, Any]]:
|
|
108
|
+
if self.current_user != LOGIN:
|
|
109
|
+
self.denials += 1
|
|
110
|
+
raise _denied(SERVER_ONLY)
|
|
111
|
+
self.server_calls += 1
|
|
112
|
+
return [{"id": "row-1"}]
|
|
113
|
+
|
|
114
|
+
async def fetch(self, sql: str, *args: Any, timeout: Any = None) -> list[dict[str, Any]]: # noqa: ASYNC109 — mirrors asyncpg's connection signature
|
|
115
|
+
self.statements.append(sql)
|
|
116
|
+
if "set_config" in sql:
|
|
117
|
+
self._apply_set_config(sql, args)
|
|
118
|
+
return [{}]
|
|
119
|
+
if SERVER_ONLY in sql:
|
|
120
|
+
return self._server_only()
|
|
121
|
+
return []
|
|
122
|
+
|
|
123
|
+
async def fetchval(self, sql: str, *args: Any, timeout: Any = None) -> Any: # noqa: ASYNC109 — mirrors asyncpg's connection signature
|
|
124
|
+
self.statements.append(sql)
|
|
125
|
+
if "current_user" in sql:
|
|
126
|
+
return self.current_user
|
|
127
|
+
if "set_config" in sql:
|
|
128
|
+
self._apply_set_config(sql, args)
|
|
129
|
+
return ""
|
|
130
|
+
if SERVER_ONLY in sql:
|
|
131
|
+
return self._server_only()[0]["id"]
|
|
132
|
+
return None
|
|
133
|
+
|
|
134
|
+
async def fetchrow(self, sql: str, *args: Any, timeout: Any = None) -> Any: # noqa: ASYNC109 — mirrors asyncpg's connection signature
|
|
135
|
+
if "set_config" in sql:
|
|
136
|
+
self._apply_set_config(sql, args)
|
|
137
|
+
if "current_user" in sql:
|
|
138
|
+
return {
|
|
139
|
+
"current_user": self.current_user,
|
|
140
|
+
"session_user": LOGIN,
|
|
141
|
+
"borrowed": self.current_user,
|
|
142
|
+
"login": LOGIN,
|
|
143
|
+
}
|
|
144
|
+
return {}
|
|
145
|
+
|
|
146
|
+
@asynccontextmanager
|
|
147
|
+
async def transaction(self): # asyncpg Connection.transaction()
|
|
148
|
+
self.in_tx = True
|
|
149
|
+
try:
|
|
150
|
+
yield
|
|
151
|
+
finally:
|
|
152
|
+
self._end_tx()
|
|
153
|
+
|
|
154
|
+
def terminate(self) -> None:
|
|
155
|
+
pass
|
|
156
|
+
|
|
157
|
+
# --- the foreign client -------------------------------------------------
|
|
158
|
+
def foreign_client_leaks_person_role(self) -> None:
|
|
159
|
+
"""What pg_stat_statements caught: set_config('role','authenticated',false)."""
|
|
160
|
+
self._apply_set_config("select set_config('role','authenticated',false)", ())
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@pytest.fixture
|
|
164
|
+
def backend(monkeypatch) -> ServerBackend:
|
|
165
|
+
b = ServerBackend()
|
|
166
|
+
|
|
167
|
+
@asynccontextmanager
|
|
168
|
+
async def get_connection(_config_name: Any, _timeout: Any = 10.0):
|
|
169
|
+
yield b
|
|
170
|
+
|
|
171
|
+
class _Adapter:
|
|
172
|
+
def get_connection(self):
|
|
173
|
+
return get_connection(None)
|
|
174
|
+
|
|
175
|
+
async def no_driver_timeout(_conn, **_kw):
|
|
176
|
+
return None
|
|
177
|
+
|
|
178
|
+
monkeypatch.setattr(AsyncDatabaseManager, "get_connection", get_connection)
|
|
179
|
+
monkeypatch.setattr(AdapterRegistry, "get", lambda _db: _Adapter())
|
|
180
|
+
monkeypatch.setattr(db_function, "resolve_driver_timeout", no_driver_timeout)
|
|
181
|
+
configure_session_context(None)
|
|
182
|
+
return b
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
async def _server_call() -> Any:
|
|
186
|
+
return await db_function.call_function("platform", "communication", SERVER_ONLY, "w1", mode="rows")
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
# ---------------------------------------------------------------------------
|
|
190
|
+
# RED before the fix: a server call outside a transaction on a dirtied backend.
|
|
191
|
+
# ---------------------------------------------------------------------------
|
|
192
|
+
async def test_server_call_on_a_backend_left_wearing_a_person_role_runs_as_the_server(backend) -> None:
|
|
193
|
+
backend.foreign_client_leaks_person_role()
|
|
194
|
+
assert backend.current_user == "authenticated"
|
|
195
|
+
|
|
196
|
+
rows = await _server_call()
|
|
197
|
+
|
|
198
|
+
assert rows == [{"id": "row-1"}]
|
|
199
|
+
assert backend.server_calls == 1
|
|
200
|
+
# Repaired transaction-locally only — the session role is not ours to change.
|
|
201
|
+
assert backend.session_role == "authenticated"
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
async def test_server_transaction_on_a_dirtied_backend_runs_as_the_login_role(backend) -> None:
|
|
205
|
+
backend.foreign_client_leaks_person_role()
|
|
206
|
+
async with transaction("platform"):
|
|
207
|
+
rows = await _server_call()
|
|
208
|
+
assert rows == [{"id": "row-1"}]
|
|
209
|
+
assert backend.denials == 0 # the BEGIN itself reset the borrowed role
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def test_every_transaction_opens_as_the_login_role_unless_rls_session_says_otherwise() -> None:
|
|
213
|
+
gucs = _opening_statement_gucs({})
|
|
214
|
+
assert gucs["role"] == "none"
|
|
215
|
+
assert list(gucs)[-1] == "role"
|
|
216
|
+
assert _opening_statement_gucs({"role": "authenticated"})["role"] == "authenticated"
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
# ---------------------------------------------------------------------------
|
|
220
|
+
# The person scope ending abnormally never leaves its role behind (our own scope).
|
|
221
|
+
# ---------------------------------------------------------------------------
|
|
222
|
+
async def test_rls_session_ended_by_an_exception_leaves_no_role(backend) -> None:
|
|
223
|
+
with pytest.raises(RuntimeError):
|
|
224
|
+
async with rls_session({"sub": "u1", "role": "authenticated"}, role="authenticated", database="platform"):
|
|
225
|
+
assert backend.current_user == "authenticated"
|
|
226
|
+
raise RuntimeError("boom")
|
|
227
|
+
assert backend.current_user == LOGIN
|
|
228
|
+
assert await _server_call() == [{"id": "row-1"}]
|
|
229
|
+
assert backend.denials == 0
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
async def test_rls_session_ended_by_cancellation_leaves_no_role(backend) -> None:
|
|
233
|
+
entered = asyncio.Event()
|
|
234
|
+
|
|
235
|
+
async def person_work() -> None:
|
|
236
|
+
async with rls_session({"sub": "u1", "role": "authenticated"}, role="authenticated", database="platform"):
|
|
237
|
+
entered.set()
|
|
238
|
+
await asyncio.sleep(10)
|
|
239
|
+
|
|
240
|
+
task = asyncio.create_task(person_work())
|
|
241
|
+
await entered.wait()
|
|
242
|
+
task.cancel()
|
|
243
|
+
with pytest.raises(asyncio.CancelledError):
|
|
244
|
+
await task
|
|
245
|
+
assert backend.current_user == LOGIN
|
|
246
|
+
assert await _server_call() == [{"id": "row-1"}]
|
|
247
|
+
assert backend.denials == 0
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
async def test_the_foreign_restrict_path_then_a_borrowed_role_still_heals(backend) -> None:
|
|
251
|
+
"""The live Agent traceback went through run_clearing_foreign_restrict."""
|
|
252
|
+
backend.foreign_client_leaks_person_role()
|
|
253
|
+
value = await fss.run_clearing_foreign_restrict(
|
|
254
|
+
backend, lambda: backend.fetchval(f"select {SERVER_ONLY}()"), what="t"
|
|
255
|
+
)
|
|
256
|
+
assert value == "row-1"
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
# ---------------------------------------------------------------------------
|
|
260
|
+
# Never hides a real denial; always loud when it repairs.
|
|
261
|
+
# ---------------------------------------------------------------------------
|
|
262
|
+
async def test_a_genuine_denial_still_raises_after_one_rerun(backend) -> None:
|
|
263
|
+
calls = 0
|
|
264
|
+
|
|
265
|
+
async def run() -> Any:
|
|
266
|
+
nonlocal calls
|
|
267
|
+
calls += 1
|
|
268
|
+
raise _denied("auth_secret")
|
|
269
|
+
|
|
270
|
+
with pytest.raises(asyncpg.exceptions.InsufficientPrivilegeError):
|
|
271
|
+
await fss.run_clearing_foreign_restrict(backend, run)
|
|
272
|
+
assert calls == 2
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
async def test_the_repair_is_loud(backend, caplog) -> None:
|
|
276
|
+
backend.foreign_client_leaks_person_role()
|
|
277
|
+
with caplog.at_level("ERROR"):
|
|
278
|
+
await _server_call()
|
|
279
|
+
assert any("authenticated" in r.getMessage() for r in caplog.records)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Guard: a test run must never file a row in the live ops.system_error.
|
|
2
|
+
|
|
3
|
+
Incident 2026-10-16/17: 14 ``rag_page_clean_degraded`` rows landed in the live error
|
|
4
|
+
log from unit tests with fake cleaners, via ``matrx_orm.record_error``.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
from matrx_orm.session import fallback
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@pytest.mark.asyncio
|
|
15
|
+
async def test_record_error_under_pytest_never_reaches_the_database(monkeypatch, captured_system_errors):
|
|
16
|
+
def _boom(*a, **k):
|
|
17
|
+
raise AssertionError("record_error reached the live database path under pytest")
|
|
18
|
+
|
|
19
|
+
# If the guard failed, the real _insert_error would resolve an adapter first.
|
|
20
|
+
monkeypatch.setattr("matrx_orm.adapters.AdapterRegistry.get", _boom)
|
|
21
|
+
await fallback.record_error(
|
|
22
|
+
RuntimeError("synthetic"), kind="guard_probe", source_feature="tests.guard"
|
|
23
|
+
)
|
|
24
|
+
assert [c["kind"] for c in captured_system_errors] == ["guard_probe"]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@pytest.mark.asyncio
|
|
28
|
+
async def test_the_guard_is_falsifiable_a_patched_insert_is_still_reached(monkeypatch, captured_system_errors):
|
|
29
|
+
seen = []
|
|
30
|
+
|
|
31
|
+
async def _fake(**kw):
|
|
32
|
+
seen.append(kw["kind"])
|
|
33
|
+
|
|
34
|
+
monkeypatch.setattr(fallback, "_insert_error", _fake)
|
|
35
|
+
await fallback.record_error(RuntimeError("x"), kind="patched_probe")
|
|
36
|
+
assert seen == ["patched_probe"]
|
|
37
|
+
assert captured_system_errors == []
|