matrx-orm 3.1.182__tar.gz → 3.1.183__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.183/CHANGELOG.md +17 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/PKG-INFO +1 -1
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/core/FEATURE.md +15 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/core/async_db_manager.py +66 -1
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/core/config.py +9 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/secrets_battery/FEATURE.md +12 -2
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/secrets_battery/items.py +17 -13
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/secrets_battery/vault_history.py +14 -1
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/pyproject.toml +1 -1
- matrx_orm-3.1.183/tests/level1/test_pool_create_never_wedges.py +194 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/test_secrets_battery.py +99 -3
- matrx_orm-3.1.182/CHANGELOG.md +0 -6
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/.gitignore +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/AGENTS.md +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/BASE_CLASS_METHODS.md +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/CLAUDE.md +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/MODEL_API.md +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/README.md +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/database/orm/extended/managers/ai_model_base.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/docs/migrations.md +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/FEATURE.md +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/MODULE_README.md +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/__init__.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/adapters/MODULE_README.md +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/adapters/__init__.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/adapters/async_postgresql.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/adapters/base_adapter.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/adapters/postgrest_client_adapter.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/adapters/supabase_adapter.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/admin/FEATURE.md +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/admin/__init__.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/admin/router.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/api/__init__.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/api/auth.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/api/config.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/api/handlers.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/api/protocol.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/api/server.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/associations.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/cache_debug.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/catalog.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/catalog_sql.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/client/MODULE_README.md +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/client/__init__.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/client/postgres_connection.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/client/postgrest.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/client/supabase_auth.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/client/supabase_config.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/client/supabase_manager.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/core/EXTENDED-TASK.md +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/core/MODULE_README.md +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/core/RELATIONS-TASKS.md +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/core/TASKS.md +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/core/__init__.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/core/admin_lane.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/core/base.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/core/connections.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/core/db_function.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/core/diagnostics.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/core/expressions.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/core/extended.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/core/fields.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/core/introspection.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/core/listen.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/core/loop_filters.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/core/model_dto.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/core/model_view.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/core/one_database.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/core/paginator.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/core/pool_maintenance.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/core/pool_watch.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/core/pooler_role.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/core/pydantic_bridge.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/core/registry.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/core/relations.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/core/resilience.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/core/rls_session.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/core/session_context.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/core/signals.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/core/tls.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/core/transaction.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/core/types.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/core/write_queue.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/core/write_retry.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/entity.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/error_handling.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/exceptions.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/extended/__init__.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/extended/app_error_handler.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/identity_rule.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/local_sqlite.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/middleware/__init__.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/middleware/base.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/migrations/MODULE_README.md +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/migrations/__init__.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/migrations/cli.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/migrations/ddl.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/migrations/diff.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/migrations/executor.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/migrations/integration.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/migrations/loader.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/migrations/operations.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/migrations/state.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/migrations/table_filter.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/no_org_assignment.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/operations/FEATURE.md +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/operations/MODULE_README.md +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/operations/__init__.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/operations/bulk_update_values.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/operations/catalog_select.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/operations/conflict.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/operations/conflict_writes.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/operations/create.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/operations/db_functions.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/operations/delete.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/operations/dynamic_admin.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/operations/dynamic_crud.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/operations/graph_walk.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/operations/latest_rows.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/operations/queue_claim.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/operations/read.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/operations/relational_backfill.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/operations/staging_load.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/operations/update.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/platform_access.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/platform_db.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/py.typed +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/pytest_live_db_guard.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/pytest_lock_guard.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/pytest_plugin.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/python_sql/MODULE_README.md +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/python_sql/__init__.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/python_sql/db_objects.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/python_sql/table_detailed_relationships.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/python_sql/table_typescript_relationship.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/query/FEATURE.md +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/query/MODULE_README.md +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/query/__init__.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/query/builder.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/query/executor.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/resources/__init__.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/resources/additional-assigners-2026-09-12.json +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/resources/org_assignment_trigger_baseline.json +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/schema_builder/FEATURE.md +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/schema_builder/MODULE_README.md +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/schema_builder/__init__.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/schema_builder/code_handler.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/schema_builder/codegen_writer.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/schema_builder/columns.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/schema_builder/common.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/schema_builder/diff_preview.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/schema_builder/entity_capabilities.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/schema_builder/generator.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/schema_builder/helpers/__init__.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/schema_builder/helpers/base_generators.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/schema_builder/helpers/entity_generators.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/schema_builder/helpers/git_checker.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/schema_builder/package_wiring.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/schema_builder/relationships.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/schema_builder/runner.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/schema_builder/schema.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/schema_builder/schema_manager.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/schema_builder/tables.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/schema_builder/views.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/secrets_battery/__init__.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/secrets_battery/attachments.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/secrets_battery/backup_restore.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/secrets_battery/crypto.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/secrets_battery/host_authorization.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/secrets_battery/model.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/secrets_battery/mutation_receipts.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/secrets_battery/native_enable.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/secrets_battery/native_lifecycle.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/secrets_battery/native_passkeys.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/secrets_battery/native_source.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/secrets_battery/org_service.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/secrets_battery/service.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/session/README.md +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/session/__init__.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/session/coalesce.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/session/dag.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/session/errors.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/session/fallback.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/session/flush.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/session/lifecycle.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/session/managed.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/session/op.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/session/reads.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/session/session.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/session/telemetry.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/sql_executor/MODULE_README.md +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/sql_executor/__init__.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/sql_executor/executor.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/sql_executor/queries.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/sql_executor/registry.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/sql_executor/types.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/sql_executor/utils.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/state.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/utils/__init__.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/utils/sql_utils.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/utils/type_converters.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/matrx_orm/write_actor.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/scripts/git-branches.sh +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/scripts/publish.sh +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/scripts/release.sh +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/MODULE_README.md +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/__init__.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/conftest.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/MODULE_README.md +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/__init__.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_adapter_registry.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_admin_db_columns.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_admin_response_rendering.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_admin_search_negation.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_admin_search_sentinels.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_advisory_lock.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_agent_message.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_api_auth.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_api_config.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_api_handlers.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_api_protocol.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_array_agg_order_by.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_association_trigger_actor_naming.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_associations.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_associations_wiring.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_bulk_hydration_offload.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_bulk_update_by_pk.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_bulk_upsert_increment_set_fields.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_cache_debug.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_call_function.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_case_when.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_catalog.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_committed_row_guard.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_composite_pk_filter.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_composite_pk_write_paths.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_config.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_conflict_target.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_conflict_writes.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_connection_codecs.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_connection_poison_guard.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_connection_primitives.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_count_composite_distinct.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_count_limit_param_leak.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_db_function_field.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_ddl_generator.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_deadlock_retry.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_derived_table.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_dirty_tracking.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_dynamic_crud.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_exceptions.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_execute_query_integrity_mapping.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_expression_primitives.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_fields.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_filter_jsonb_agg_array_index.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_fk_cycle_resolution.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_from_alias.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_fts.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_func_expression_args.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_graph_walk.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_having_annotate_alias.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_insert_ignore_and_admin_primitives.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_introspect_rls_policies.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_join_and_aggregate.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_latest_rows.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_listen_notify.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_local_sqlite.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_lock_contention_retry.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_managed_write_guard.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_matrx_entity.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_migration_diff_types.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_migration_loader.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_model_instance.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_model_meta.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_optimistic_concurrency.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_order_by_expression.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_org_assignment_inventory.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_orm_banners_are_client_safe.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_parameter_name_hydration.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_patch_jsonb_path.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_percentile_cont.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_persisted_count.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_pool_holder_census.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_pool_loop_recreation.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_pool_maintenance.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_postgrest_filters.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_pydantic_bridge.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_query_builder.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_query_error_detail.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_query_executor_sql.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_query_timeout.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_querybuilder_clone_semantics.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_queue_claim.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_read_connection_retry.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_registry.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_registry_multi_database.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_registry_multi_schema.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_relational_backfill.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_relations.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_resilience.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_rls_session_scope.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_schema_exists.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_session_advisory_lock.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_standalone_write_session_context.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_state_cache.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_subquery_filter_raw.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_subquery_in_filter.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_subquery_nested_expressions.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_supabase_auth.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_supabase_config.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_tls.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_transaction_database_identity.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_typed_join_projection.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_typed_predicates.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_update_case_expression.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_update_subquery.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_update_with_rebase.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_upsert_default_leak.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_upsert_with_conflict.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_values_jsonb_decode.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_watched_lifecycle.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_write_queue_deadline.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level1/test_write_retry_semantics.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level2/MODULE_README.md +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level2/__init__.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level2/conftest.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level2/test_bulk_ops.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level2/test_cache_integration.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level2/test_crud.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level2/test_foreign_keys.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level2/test_m2m.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level2/test_manager.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level2/test_migrations_live.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level2/test_query_execution.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level2/test_schema_diff.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/level2/verify_committed_row_guard_pg17.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/live_pooler_role.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/native_source_cases.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/native_source_oracle.rs +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/sample_project/.env.example +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/sample_project/README.md +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/sample_project/__init__.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/sample_project/generate.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/sample_project/generated/.gitkeep +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/sample_project/matrx_orm.yaml +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/sample_project/test_schema_generation.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/sample_project_desktop/.env.example +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/sample_project_desktop/README.md +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/sample_project_desktop/__init__.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/sample_project_desktop/client_example.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/sample_project_desktop/client_example.ts +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/sample_project_desktop/client_supabase_example.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/sample_project_desktop/server.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/schema/entity_tests.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/schema/test_base_generation.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/schema/test_composite_pk_fk_generation.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/schema/test_composite_unique_generation.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/schema/test_dto_identity_generation.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/schema/test_entity_capability_generation.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/schema/test_enum_ordering.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/schema/test_generate_schema.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/schema/test_junction_analysis.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/schema/test_multi_schema_output.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/schema/test_schema_analysis.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/schema/test_sql_expression_defaults.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/schema/test_table_relationships_query.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/schema/test_view_generation.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/schema_builder/test_array_default_parsing.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/schema_builder/test_focused_generation_plan.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/schema_builder/test_host_models_aggregator.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/session/__init__.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/session/conftest.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/session/test_a_failed_capture_still_names_the_failure.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/session/test_cache_no_downgrade.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/session/test_capture_poison_proof.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/session/test_coalesce.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/session/test_dag.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/session/test_disk_spill_fallback.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/session/test_flush_individual_fallback.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/session/test_governed_write.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/session/test_live_db_guard_plugin.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/session/test_managed.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/session/test_op_enqueue_site.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/session/test_reads.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/session/test_record_error_bounded_insert.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/session/test_record_error_storm.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/session/test_session.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/session/test_spill_guard_plugin.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/test_array_arg_element_type.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/test_attachment_mutation_serialization.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/test_backup_alias_conflict.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/test_backup_restore_live.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/test_composite_fk_is_not_a_column_fk.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/test_database_name_alias.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/test_db_functions.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/test_dsn_password_masking.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/test_dynamic_select_unregistered.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/test_immutable_insert_or_exact_verify.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/test_keyed_digest.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/test_model_cls_refactor.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/test_native_enable.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/test_native_lifecycle.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/test_native_lifecycle_fences.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/test_native_passkey_receipts.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/test_native_source.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/test_no_blocking_asyncpg_tls.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/test_one_database.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/test_order_by_desc_nulls_last.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/test_pooler_role_guard.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/test_secret_audit_names_its_organization.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/test_secrets_minting_keys.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/test_shared_lock_guard.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/test_sql_param_casts.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/test_sync_twins_guard.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/test_user_visible_lookup.py +0 -0
- {matrx_orm-3.1.182 → matrx_orm-3.1.183}/tests/test_vault_aggregate_recovery_stateful.py +0 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
- Pool creation always ends. A pool `init` failure now aborts the connection
|
|
6
|
+
(asyncpg's own failure path awaited an untimed graceful close), and
|
|
7
|
+
`asyncpg.create_pool` runs under one deadline (`pool_create_timeout`, default
|
|
8
|
+
30 s; `connect_timeout`, default 10 s, both on `DatabaseProjectConfig`), with
|
|
9
|
+
the half-built pool terminated on failure. Before this, a database that hung
|
|
10
|
+
mid-handshake held the per-config pool lock forever: files.matrxserver.com
|
|
11
|
+
answered nothing that touched the database for 4.5 h on 2026-09-25.
|
|
12
|
+
Guard: `tests/level1/test_pool_create_never_wedges.py`.
|
|
13
|
+
|
|
14
|
+
## 3.1.182
|
|
15
|
+
|
|
16
|
+
- 3.1.181 was tagged against a commit that did not carry the version and never
|
|
17
|
+
published; 3.1.182 is the same code plus everything since.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: matrx-orm
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.183
|
|
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
|
|
@@ -169,6 +169,20 @@ connection and can strand old holders in release/reset.
|
|
|
169
169
|
|
|
170
170
|
Pinned by `tests/level1/test_pool_maintenance.py`.
|
|
171
171
|
|
|
172
|
+
**Pool creation always ends.** `get_pool` creates under the config's lock, so an
|
|
173
|
+
unbounded creation wedges every caller of that config. Two layers: (1) the
|
|
174
|
+
`init` callback (`_init_connection_default`) ABORTS its connection on any
|
|
175
|
+
failure or cancellation — asyncpg's own failure path awaits `con.close()` with
|
|
176
|
+
no timeout, after the one cancellation an outer timeout could deliver was
|
|
177
|
+
already spent, and against a hung database that close never ends; (2)
|
|
178
|
+
`asyncpg.create_pool` runs under `pool_create_timeout` (default 30 s) with
|
|
179
|
+
`connect_timeout` (default 10 s), and the half-built pool is terminated on
|
|
180
|
+
failure. Incident 2026-09-25: the zero-connection replacement ran while the
|
|
181
|
+
database machine was hung and held `_locks['matrx_files']` for 4.5 h (the same
|
|
182
|
+
close wedged matrx-seo's keepalive). Pinned by
|
|
183
|
+
`tests/level1/test_pool_create_never_wedges.py` (a fake PostgreSQL that
|
|
184
|
+
handshakes, then goes silent).
|
|
185
|
+
|
|
172
186
|
**A server restart rejected during checkout is a connection failure.** PostgreSQL
|
|
173
187
|
`AdminShutdownError`, `CrashShutdownError`, and `CannotConnectNowError` raised
|
|
174
188
|
while `pool.acquire()` opens a physical connection become `ConnectionError` at
|
|
@@ -370,5 +384,6 @@ Verified against the live database: 2026-08-21.
|
|
|
370
384
|
- 2026-08-14: Public models now render `public.table` SQL targets; bare public
|
|
371
385
|
relation names can no longer disable application logs or write-failure capture.
|
|
372
386
|
- 2026-08-13: Registry, admin, recovery, catalog, and diagnostic identities are now exact `schema.table`; bare names are accepted only as uncached discovery terms and ambiguity fails loudly.
|
|
387
|
+
- 2026-09-25: Pool creation is bounded and a failed connection init aborts instead of awaiting an untimed close.
|
|
373
388
|
- 2026-08-13: Added atomic recovery from a pool left at zero connections after an upstream database restart.
|
|
374
389
|
- 2026-08-09: Pinned positional-mapping hydration for parameter-shaped column names.
|
|
@@ -119,6 +119,11 @@ def _integrity_constraint_kind(exc: asyncpg.exceptions.IntegrityConstraintViolat
|
|
|
119
119
|
POOL_ACQUIRE_RETRIES = 2 # total acquire attempts = 1 + POOL_ACQUIRE_RETRIES
|
|
120
120
|
POOL_ACQUIRE_RETRY_BASE_BACKOFF = 0.25 # seconds; doubled per attempt
|
|
121
121
|
POOL_RELEASE_TIMEOUT_SECONDS = 10.0
|
|
122
|
+
# Pool-creation bounds (per-config overrides: ``connect_timeout`` /
|
|
123
|
+
# ``pool_create_timeout`` on DatabaseProjectConfig). Creation runs under the
|
|
124
|
+
# config's pool lock, so it must always end — see _init_connection_default.
|
|
125
|
+
DEFAULT_CONNECT_TIMEOUT_SECONDS = 10.0
|
|
126
|
+
DEFAULT_POOL_CREATE_TIMEOUT_SECONDS = 30.0
|
|
122
127
|
|
|
123
128
|
# ── read-statement retry (command-timeout self-heal) ──────────────────────────
|
|
124
129
|
# A ``command_timeout`` on a READ (SELECT) is provably side-effect-free to retry
|
|
@@ -257,6 +262,30 @@ def _encode_json(value: Any) -> str:
|
|
|
257
262
|
|
|
258
263
|
|
|
259
264
|
async def _init_connection_default(conn: asyncpg.Connection) -> None:
|
|
265
|
+
"""Register the codecs on a new connection; a failure ABORTS it.
|
|
266
|
+
|
|
267
|
+
🚨 THE 2026-09-25 FILES OUTAGE. When a pool ``init`` callback raises,
|
|
268
|
+
asyncpg's ``Pool._get_new_connection`` runs ``await con.close()`` with NO
|
|
269
|
+
timeout before re-raising. Against a database that has stopped answering
|
|
270
|
+
(a hung machine: handshake done, then silence, no FIN, no RST) that graceful
|
|
271
|
+
close never finishes — and when it happens inside ``asyncpg.create_pool``,
|
|
272
|
+
matrx-orm's per-config pool-creation lock is held forever and every later
|
|
273
|
+
request queues behind it without a log line. files.matrxserver.com sat
|
|
274
|
+
exactly there for 4.5 hours. Aborting the transport here makes the
|
|
275
|
+
connection already closed, so asyncpg's ``close()`` returns at once and the
|
|
276
|
+
original error reaches the caller.
|
|
277
|
+
"""
|
|
278
|
+
try:
|
|
279
|
+
await _register_connection_codecs(conn)
|
|
280
|
+
except BaseException:
|
|
281
|
+
try:
|
|
282
|
+
conn.terminate()
|
|
283
|
+
except Exception: # noqa: BLE001 — abort is best-effort; the original error wins
|
|
284
|
+
pass
|
|
285
|
+
raise
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
async def _register_connection_codecs(conn: asyncpg.Connection) -> None:
|
|
260
289
|
"""Register custom type codecs on each new connection.
|
|
261
290
|
|
|
262
291
|
UUID codec — makes asyncpg decode PostgreSQL uuid columns directly as Python
|
|
@@ -610,7 +639,21 @@ class AsyncDatabaseManager:
|
|
|
610
639
|
# Bootstrap exactly one connection; the bounded
|
|
611
640
|
# maintenance controller ramps toward the desired floor.
|
|
612
641
|
bootstrap_min = min(desired_pool_min, 1)
|
|
613
|
-
|
|
642
|
+
connect_timeout = float(
|
|
643
|
+
config.get("connect_timeout") or DEFAULT_CONNECT_TIMEOUT_SECONDS
|
|
644
|
+
)
|
|
645
|
+
create_timeout = float(
|
|
646
|
+
config.get("pool_create_timeout")
|
|
647
|
+
or DEFAULT_POOL_CREATE_TIMEOUT_SECONDS
|
|
648
|
+
)
|
|
649
|
+
# 🚨 Pool creation runs under this config's lock, so it
|
|
650
|
+
# must END. asyncpg bounds the connect itself
|
|
651
|
+
# (``timeout=``) but not the ``init`` callback's failure
|
|
652
|
+
# path nor the pool as a whole; a hung database once held
|
|
653
|
+
# this lock for 4.5 hours (see _init_connection_default).
|
|
654
|
+
# Build the pool object, await it under one deadline,
|
|
655
|
+
# and terminate whatever it half-built on any failure.
|
|
656
|
+
new_pool = asyncpg.create_pool(
|
|
614
657
|
host=config["host"],
|
|
615
658
|
port=config["port"],
|
|
616
659
|
database=config["database_name"],
|
|
@@ -624,7 +667,29 @@ class AsyncDatabaseManager:
|
|
|
624
667
|
statement_cache_size=statement_cache_size,
|
|
625
668
|
server_settings=server_settings,
|
|
626
669
|
init=_init_connection_default,
|
|
670
|
+
timeout=connect_timeout,
|
|
627
671
|
)
|
|
672
|
+
try:
|
|
673
|
+
async with asyncio.timeout(create_timeout):
|
|
674
|
+
pool = await new_pool
|
|
675
|
+
except BaseException as create_exc:
|
|
676
|
+
try:
|
|
677
|
+
new_pool.terminate()
|
|
678
|
+
except Exception: # noqa: BLE001 — a pool that never initialised has nothing to abort
|
|
679
|
+
pass
|
|
680
|
+
if isinstance(create_exc, TimeoutError):
|
|
681
|
+
logger.error(
|
|
682
|
+
"[matrx_orm] Pool creation for '%s' did not finish within "
|
|
683
|
+
"%.1fs (connect timeout %.1fs) — the database accepted "
|
|
684
|
+
"the connection but stopped answering. The half-built "
|
|
685
|
+
"pool was terminated; the next request tries again.",
|
|
686
|
+
config_name,
|
|
687
|
+
create_timeout,
|
|
688
|
+
connect_timeout,
|
|
689
|
+
)
|
|
690
|
+
# A TimeoutError is classified below as a transport
|
|
691
|
+
# timeout (DatabaseConnectError) — one honest banner.
|
|
692
|
+
raise
|
|
628
693
|
cls._pools[config_name] = pool
|
|
629
694
|
|
|
630
695
|
logger.info(
|
|
@@ -53,6 +53,13 @@ class DatabaseProjectConfig:
|
|
|
53
53
|
pool_min: int = 5
|
|
54
54
|
pool_max: int = 30
|
|
55
55
|
command_timeout: int = 10
|
|
56
|
+
# Bounds on opening a pool. ``connect_timeout`` is asyncpg's per-connection
|
|
57
|
+
# connect deadline; ``pool_create_timeout`` bounds the whole creation
|
|
58
|
+
# (connect + the init callback + asyncpg's own cleanup). Creation holds the
|
|
59
|
+
# config's pool lock, so an unbounded creation wedges every caller: the
|
|
60
|
+
# 2026-09-25 files.matrxserver.com outage (4.5 h behind one stuck create).
|
|
61
|
+
connect_timeout: float = 10.0
|
|
62
|
+
pool_create_timeout: float = 30.0
|
|
56
63
|
|
|
57
64
|
# Maximum seconds an idle connection may sit in the pool before it is
|
|
58
65
|
# closed and replaced. asyncpg's own default is 300s, which is *longer*
|
|
@@ -240,6 +247,8 @@ class DatabaseRegistry:
|
|
|
240
247
|
"pool_min": config.pool_min,
|
|
241
248
|
"pool_max": config.pool_max,
|
|
242
249
|
"command_timeout": config.command_timeout,
|
|
250
|
+
"connect_timeout": config.connect_timeout,
|
|
251
|
+
"pool_create_timeout": config.pool_create_timeout,
|
|
243
252
|
"max_inactive_connection_lifetime": config.max_inactive_connection_lifetime,
|
|
244
253
|
"server_settings": dict(config.server_settings),
|
|
245
254
|
"ssl": config.ssl,
|
|
@@ -43,8 +43,12 @@ keys, typed identifiers and timestamp, locked original ownership, and empty
|
|
|
43
43
|
component arrays. Organization-owned history refuses until the host supplies the
|
|
44
44
|
grant/offboarding parent-lock contract. Future aggregate capture may use the
|
|
45
45
|
revision-1/revision-2 bootstrap only after its producer, authority, retention,
|
|
46
|
-
rotation, and reader gates are independently accepted. Gate B
|
|
47
|
-
|
|
46
|
+
rotation, and reader gates are independently accepted. Gate B's source-only
|
|
47
|
+
1047 validator freezes one personal, active ordinary password field shape: its
|
|
48
|
+
first recorded update is an A=A bootstrap followed by A→B, and later records
|
|
49
|
+
are one prior-after-matched delta each. Application capture is explicitly off
|
|
50
|
+
until every producer and reader gate is accepted; 1047 is never applied outside
|
|
51
|
+
the named runner's reviewed rehearsal.
|
|
48
52
|
|
|
49
53
|
Items may also own N encrypted **file attachments**
|
|
50
54
|
(`users.credential_attachments`). An attachment has its own stable id,
|
|
@@ -156,6 +160,8 @@ drops every grantee when `access_mode='all_members'` — it survives for the org
|
|
|
156
160
|
access-mode flip, but building new callers on it is how a share edit silently
|
|
157
161
|
revokes someone. `list_granted_credential_items(user_id)` is the
|
|
158
162
|
"shared with me" read.
|
|
163
|
+
`set_item_grants` locks the live parent before deleting or replacing grants;
|
|
164
|
+
host readers and revokers therefore serialize parent-first.
|
|
159
165
|
|
|
160
166
|
`transfer_credential_item` clears prior grants by default (`clear_grants=True`)
|
|
161
167
|
and writes BOTH `item_transferred` and `item_transferred_out`, so the losing
|
|
@@ -381,6 +387,10 @@ delete).
|
|
|
381
387
|
|
|
382
388
|
## Change Log
|
|
383
389
|
|
|
390
|
+
- **2026-09-25** — `set_item_grants` now locks the live parent before grant
|
|
391
|
+
replacement, matching the host bulk reader's revocation order. A focused
|
|
392
|
+
ordering test fails if deletion moves ahead of the parent lock.
|
|
393
|
+
|
|
384
394
|
- **2026-09-24** — Migration 1036 closes the personal-only Vault revision envelope and refuses organization history pending the canonical grant/offboarding parent lock; capture remains disabled.
|
|
385
395
|
|
|
386
396
|
- **2026-09-24** — Kept the history counter out of the ORM until migrations are installed; the branch database lacks the column and a premature model mapping broke ordinary Vault creation. Capture remains disabled.
|
|
@@ -1327,7 +1327,7 @@ async def update_credential_field_value(
|
|
|
1327
1327
|
_PASSWORD_HISTORY_CAPTURE_ENABLED and not system_rotation
|
|
1328
1328
|
and item_raw is not None and item_raw.get("user_id") is not None
|
|
1329
1329
|
and raw.get("field_key") == "password" and raw.get("handling") in {"visible", "revealable"}
|
|
1330
|
-
and raw.get("execution_purpose") == GENERAL_EXECUTION_PURPOSE
|
|
1330
|
+
and raw.get("execution_purpose") == GENERAL_EXECUTION_PURPOSE and raw.get("is_active") is True
|
|
1331
1331
|
)
|
|
1332
1332
|
history_revision: int | None = None
|
|
1333
1333
|
capture_started_at = datetime.now(UTC)
|
|
@@ -1335,17 +1335,18 @@ async def update_credential_field_value(
|
|
|
1335
1335
|
if not actor_id or not audit_organization_id:
|
|
1336
1336
|
raise VaultHistorySnapshotError("personal password history requires actor and admitted audit organization")
|
|
1337
1337
|
history_revision = await locked_history_revision(str(item_raw["id"]))
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1338
|
+
if history_revision == 0:
|
|
1339
|
+
before = password_field_update_snapshot(
|
|
1340
|
+
item_id=str(item_raw["id"]), item_user_id=str(item_raw["user_id"]),
|
|
1341
|
+
audit_organization_id=audit_organization_id, field_id=field_id,
|
|
1342
|
+
field_key=str(raw["field_key"]), handling=str(raw["handling"]),
|
|
1343
|
+
execution_purpose=str(raw["execution_purpose"]), before_encrypted=raw["value_encrypted"],
|
|
1344
|
+
after_encrypted=raw["value_encrypted"], capture_started_at=capture_started_at,
|
|
1345
|
+
)
|
|
1346
|
+
history_revision = await write_password_field_update_snapshot(
|
|
1347
|
+
item_id=str(item_raw["id"]), actor_id=actor_id, audit_organization_id=audit_organization_id,
|
|
1348
|
+
expected_revision=history_revision, snapshot=before,
|
|
1349
|
+
)
|
|
1349
1350
|
encrypted_value = encrypt_value(value)
|
|
1350
1351
|
await UserSecret.update_where(
|
|
1351
1352
|
{"id": field_id, "deleted_at__isnull": True},
|
|
@@ -1800,7 +1801,6 @@ async def set_item_grants(
|
|
|
1800
1801
|
recipient is a legitimate member first."""
|
|
1801
1802
|
if access_mode not in ("all_members", "restricted"):
|
|
1802
1803
|
raise ValueError(f"access_mode must be all_members|restricted; got {access_mode!r}")
|
|
1803
|
-
item_raw = await _require_item_raw(item_id)
|
|
1804
1804
|
# Dedup by user_id, last spec wins.
|
|
1805
1805
|
deduped: dict[str, ItemGrantSpec] = {}
|
|
1806
1806
|
if access_mode == "restricted":
|
|
@@ -1810,6 +1810,10 @@ async def set_item_grants(
|
|
|
1810
1810
|
deduped[g.user_id] = g
|
|
1811
1811
|
recipients = [deduped[uid] for uid in sorted(deduped)]
|
|
1812
1812
|
async with transaction(PACKAGE_DB_NAME):
|
|
1813
|
+
# Readers and revokers share parent-first serialization. Deleting grant
|
|
1814
|
+
# rows before this lock inverts the order and can deadlock with a
|
|
1815
|
+
# plaintext reader holding the parent while acquiring its grant.
|
|
1816
|
+
item_raw = await _lock_item_raw(item_id)
|
|
1813
1817
|
await UserSecretGrant.delete_where(credential_item_id=item_id)
|
|
1814
1818
|
for g in recipients:
|
|
1815
1819
|
await UserSecretGrant.create(
|
|
@@ -121,12 +121,25 @@ def validate_snapshot_v1(snapshot: dict[str, Any]) -> None:
|
|
|
121
121
|
raise VaultHistorySnapshotError("Vault history state is not valid encrypted v1 data")
|
|
122
122
|
|
|
123
123
|
|
|
124
|
+
def validate_snapshot_revision(snapshot: dict[str, Any], expected_revision: int) -> None:
|
|
125
|
+
"""Bind the bootstrap shape to revision zero and every later shape to a delta."""
|
|
126
|
+
validate_snapshot_v1(snapshot)
|
|
127
|
+
if expected_revision < 0:
|
|
128
|
+
raise VaultHistorySnapshotError("Vault history revision must be non-negative")
|
|
129
|
+
change = snapshot["changed_components"][0]
|
|
130
|
+
same_state = change["before"] == change["after"]
|
|
131
|
+
if expected_revision == 0 and not same_state:
|
|
132
|
+
raise VaultHistorySnapshotError("Vault history revision one must be the A=A bootstrap")
|
|
133
|
+
if expected_revision > 0 and same_state:
|
|
134
|
+
raise VaultHistorySnapshotError("Vault history revisions after bootstrap must be deltas")
|
|
135
|
+
|
|
136
|
+
|
|
124
137
|
async def write_password_field_update_snapshot(
|
|
125
138
|
*, item_id: str, actor_id: str, audit_organization_id: str, expected_revision: int,
|
|
126
139
|
snapshot: dict[str, Any],
|
|
127
140
|
) -> int:
|
|
128
141
|
"""Call the 1034 private writer inside the caller's active transaction."""
|
|
129
|
-
|
|
142
|
+
validate_snapshot_revision(snapshot, expected_revision)
|
|
130
143
|
rows = await call_function(
|
|
131
144
|
PACKAGE_DB_NAME, "history", "vault_write_revision", item_id, actor_id,
|
|
132
145
|
audit_organization_id, expected_revision, snapshot, mode="rows",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "matrx-orm"
|
|
3
|
-
version = "3.1.
|
|
3
|
+
version = "3.1.183"
|
|
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" }
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"""A pool created against a database that stops answering must fail in bounded time.
|
|
2
|
+
|
|
3
|
+
Incident 2026-09-25 (files.matrxserver.com): the production database machine hung
|
|
4
|
+
14:30–14:38 UTC. matrx-orm's zero-connection self-heal replaced the dead pool and
|
|
5
|
+
called ``asyncpg.create_pool`` while the database was still hung. The new
|
|
6
|
+
connection completed its handshake, then the pool ``init`` callback
|
|
7
|
+
(``_init_connection_default`` -> ``set_type_codec`` introspection) timed out, and
|
|
8
|
+
asyncpg's failure path ran ``await con.close()`` with NO timeout. The graceful
|
|
9
|
+
close never finished, ``create_pool`` never returned, and the per-config
|
|
10
|
+
pool-creation lock stayed held for 4.5 hours: every request queued behind it with
|
|
11
|
+
no log and no socket, until the container was restarted.
|
|
12
|
+
|
|
13
|
+
This test reproduces that exact wire situation with a fake PostgreSQL server
|
|
14
|
+
that completes the startup handshake and then goes silent forever (it never
|
|
15
|
+
answers a query and never closes the socket — what a hung machine looks like to
|
|
16
|
+
a client). It drives the REAL ``AsyncDatabaseManager.get_connection`` ->
|
|
17
|
+
``get_pool`` -> ``asyncpg.create_pool`` path with the real ``init`` callback.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import asyncio
|
|
23
|
+
import struct
|
|
24
|
+
import time
|
|
25
|
+
|
|
26
|
+
import pytest
|
|
27
|
+
from matrx_orm.core.async_db_manager import AsyncDatabaseManager
|
|
28
|
+
from matrx_orm.core.config import DatabaseProjectConfig, register_database, registry
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _msg(kind: bytes, payload: bytes) -> bytes:
|
|
32
|
+
return kind + struct.pack("!I", len(payload) + 4) + payload
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _param(name: str, value: str) -> bytes:
|
|
36
|
+
return _msg(b"S", name.encode() + b"\0" + value.encode() + b"\0")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class HungAfterHandshakeServer:
|
|
40
|
+
"""Speaks the PostgreSQL startup protocol, then never answers again."""
|
|
41
|
+
|
|
42
|
+
def __init__(self) -> None:
|
|
43
|
+
self.connections = 0
|
|
44
|
+
self.writers: list[asyncio.StreamWriter] = []
|
|
45
|
+
self.server: asyncio.base_events.Server | None = None
|
|
46
|
+
self.port = 0
|
|
47
|
+
|
|
48
|
+
async def start(self) -> None:
|
|
49
|
+
self.server = await asyncio.start_server(self._handle, "127.0.0.1", 0)
|
|
50
|
+
self.port = self.server.sockets[0].getsockname()[1]
|
|
51
|
+
|
|
52
|
+
async def _handle(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None:
|
|
53
|
+
self.connections += 1
|
|
54
|
+
self.writers.append(writer)
|
|
55
|
+
try:
|
|
56
|
+
(length,) = struct.unpack("!I", await reader.readexactly(4))
|
|
57
|
+
await reader.readexactly(length - 4) # StartupMessage (ssl disabled)
|
|
58
|
+
writer.write(
|
|
59
|
+
_msg(b"R", struct.pack("!I", 0)) # AuthenticationOk
|
|
60
|
+
+ _param("server_version", "17.0")
|
|
61
|
+
+ _param("server_encoding", "UTF8")
|
|
62
|
+
+ _param("client_encoding", "UTF8")
|
|
63
|
+
+ _param("DateStyle", "ISO, MDY")
|
|
64
|
+
+ _param("integer_datetimes", "on")
|
|
65
|
+
+ _param("standard_conforming_strings", "on")
|
|
66
|
+
+ _msg(b"K", struct.pack("!II", 1234, 5678)) # BackendKeyData
|
|
67
|
+
+ _msg(b"Z", b"I") # ReadyForQuery
|
|
68
|
+
)
|
|
69
|
+
await writer.drain()
|
|
70
|
+
# Hung machine: read and drop everything (queries, Sync, Terminate);
|
|
71
|
+
# never reply, never close.
|
|
72
|
+
while await reader.read(65536):
|
|
73
|
+
pass
|
|
74
|
+
except (asyncio.IncompleteReadError, ConnectionError):
|
|
75
|
+
pass
|
|
76
|
+
# Peer closed: stay silent, keep our side open until the test ends.
|
|
77
|
+
await asyncio.Event().wait()
|
|
78
|
+
|
|
79
|
+
async def stop(self) -> None:
|
|
80
|
+
for w in self.writers:
|
|
81
|
+
w.transport.abort()
|
|
82
|
+
if self.server is not None:
|
|
83
|
+
self.server.close()
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _register(name: str, port: int, *, command_timeout: int = 1, create_timeout: float = 4.0) -> None:
|
|
87
|
+
registry._configs.pop(name, None)
|
|
88
|
+
register_database(
|
|
89
|
+
DatabaseProjectConfig(
|
|
90
|
+
name=name,
|
|
91
|
+
alias=f"{name}_alias",
|
|
92
|
+
host="127.0.0.1",
|
|
93
|
+
port=str(port),
|
|
94
|
+
database_name="postgres",
|
|
95
|
+
user="postgres",
|
|
96
|
+
password="unused",
|
|
97
|
+
pool_min=1,
|
|
98
|
+
pool_max=2,
|
|
99
|
+
command_timeout=command_timeout,
|
|
100
|
+
ssl=False,
|
|
101
|
+
write_queue_enabled=False,
|
|
102
|
+
connect_timeout=2.0,
|
|
103
|
+
pool_create_timeout=create_timeout,
|
|
104
|
+
)
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _forget(name: str) -> None:
|
|
109
|
+
pool = AsyncDatabaseManager._pools.pop(name, None)
|
|
110
|
+
if pool is not None:
|
|
111
|
+
try:
|
|
112
|
+
pool.terminate()
|
|
113
|
+
except Exception:
|
|
114
|
+
pass
|
|
115
|
+
AsyncDatabaseManager._pool_loops.pop(name, None)
|
|
116
|
+
AsyncDatabaseManager._locks.pop(name, None)
|
|
117
|
+
AsyncDatabaseManager._growth_locks.pop(name, None)
|
|
118
|
+
task = AsyncDatabaseManager._keepalive_tasks.pop(name, None)
|
|
119
|
+
if task is not None:
|
|
120
|
+
task.cancel()
|
|
121
|
+
registry._configs.pop(name, None)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@pytest.mark.asyncio
|
|
125
|
+
@pytest.mark.parametrize(
|
|
126
|
+
("command_timeout", "create_timeout"),
|
|
127
|
+
[
|
|
128
|
+
# init's introspection times out first (files.matrxserver.com, 14:32 UTC)
|
|
129
|
+
(1, 4.0),
|
|
130
|
+
# the outer deadline CANCELS init first. asyncpg then awaits its cleanup
|
|
131
|
+
# close() after the one cancellation was already spent — the trap that
|
|
132
|
+
# also wedged matrx-seo's keepalive (maintain_one_connection's 5 s timeout
|
|
133
|
+
# cancelled init; the close that followed hung for 4.5 h).
|
|
134
|
+
(30, 2.0),
|
|
135
|
+
],
|
|
136
|
+
ids=["init-times-out", "deadline-cancels-init"],
|
|
137
|
+
)
|
|
138
|
+
async def test_pool_creation_against_a_hung_database_fails_in_bounded_time_and_releases_the_lock(
|
|
139
|
+
command_timeout: int, create_timeout: float
|
|
140
|
+
):
|
|
141
|
+
name = f"hung_after_handshake_{command_timeout}"
|
|
142
|
+
server = HungAfterHandshakeServer()
|
|
143
|
+
await server.start()
|
|
144
|
+
_register(name, server.port, command_timeout=command_timeout, create_timeout=create_timeout)
|
|
145
|
+
try:
|
|
146
|
+
# First caller: must FAIL (not hang) well inside the create deadline + slack.
|
|
147
|
+
started = time.monotonic()
|
|
148
|
+
with pytest.raises(Exception):
|
|
149
|
+
async with asyncio.timeout(15): # the test's own backstop, far above the knob
|
|
150
|
+
async with AsyncDatabaseManager.get_connection(name, timeout=1.0):
|
|
151
|
+
pass
|
|
152
|
+
first_elapsed = time.monotonic() - started
|
|
153
|
+
assert first_elapsed < 10, f"pool creation took {first_elapsed:.1f}s against a hung DB"
|
|
154
|
+
|
|
155
|
+
# The pool-creation lock must be free again: a wedged lock is the outage.
|
|
156
|
+
lock = AsyncDatabaseManager._locks.get(name)
|
|
157
|
+
assert lock is not None and not lock.locked(), "pool-creation lock left held"
|
|
158
|
+
assert name not in AsyncDatabaseManager._pools, "a half-initialised pool was kept"
|
|
159
|
+
|
|
160
|
+
# Second caller reaches the database again (a new connection attempt), and
|
|
161
|
+
# also fails in bounded time — nobody queues forever behind the first.
|
|
162
|
+
before = server.connections
|
|
163
|
+
started = time.monotonic()
|
|
164
|
+
with pytest.raises(Exception):
|
|
165
|
+
async with asyncio.timeout(15):
|
|
166
|
+
async with AsyncDatabaseManager.get_connection(name, timeout=1.0):
|
|
167
|
+
pass
|
|
168
|
+
assert time.monotonic() - started < 10
|
|
169
|
+
assert server.connections > before, "second caller never reached the database"
|
|
170
|
+
finally:
|
|
171
|
+
_forget(name)
|
|
172
|
+
await server.stop()
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
@pytest.mark.asyncio
|
|
176
|
+
@pytest.mark.parametrize("failure", [TimeoutError, asyncio.CancelledError])
|
|
177
|
+
async def test_failed_init_aborts_the_connection_instead_of_a_graceful_close_that_can_hang(failure):
|
|
178
|
+
"""The init callback's own failure — or its cancellation — leaves asyncpg nothing to wait on."""
|
|
179
|
+
from matrx_orm.core.async_db_manager import _init_connection_default
|
|
180
|
+
|
|
181
|
+
class _Conn:
|
|
182
|
+
def __init__(self) -> None:
|
|
183
|
+
self.terminated = False
|
|
184
|
+
|
|
185
|
+
async def set_type_codec(self, *_a, **_k):
|
|
186
|
+
raise failure("introspection timed out on a hung database")
|
|
187
|
+
|
|
188
|
+
def terminate(self) -> None:
|
|
189
|
+
self.terminated = True
|
|
190
|
+
|
|
191
|
+
conn = _Conn()
|
|
192
|
+
with pytest.raises(failure):
|
|
193
|
+
await _init_connection_default(conn) # type: ignore[arg-type]
|
|
194
|
+
assert conn.terminated, "a failed init must abort the connection"
|
|
@@ -11,6 +11,7 @@ from __future__ import annotations
|
|
|
11
11
|
|
|
12
12
|
import base64
|
|
13
13
|
import hashlib
|
|
14
|
+
from contextlib import asynccontextmanager
|
|
14
15
|
from datetime import UTC, datetime
|
|
15
16
|
|
|
16
17
|
import pytest
|
|
@@ -880,6 +881,35 @@ async def test_set_item_grants_validates_access_mode_before_touching_db():
|
|
|
880
881
|
await items.set_item_grants("i1", actor_id="u1", access_mode="nope", grants=[])
|
|
881
882
|
|
|
882
883
|
|
|
884
|
+
@pytest.mark.asyncio
|
|
885
|
+
async def test_set_item_grants_locks_parent_before_deleting_grants(monkeypatch):
|
|
886
|
+
from matrx_orm.secrets_battery import items
|
|
887
|
+
|
|
888
|
+
events: list[str] = []
|
|
889
|
+
|
|
890
|
+
@asynccontextmanager
|
|
891
|
+
async def fake_transaction(_database):
|
|
892
|
+
yield
|
|
893
|
+
|
|
894
|
+
async def lock_parent(_item_id):
|
|
895
|
+
events.append("parent")
|
|
896
|
+
return {"id": "i1"}
|
|
897
|
+
|
|
898
|
+
async def delete_grants(**_filters):
|
|
899
|
+
events.append("grant_delete")
|
|
900
|
+
raise RuntimeError("stop after order proof")
|
|
901
|
+
|
|
902
|
+
monkeypatch.setattr(items, "transaction", fake_transaction)
|
|
903
|
+
monkeypatch.setattr(items, "_lock_item_raw", lock_parent)
|
|
904
|
+
monkeypatch.setattr(items.UserSecretGrant, "delete_where", delete_grants)
|
|
905
|
+
|
|
906
|
+
with pytest.raises(RuntimeError, match="order proof"):
|
|
907
|
+
await items.set_item_grants(
|
|
908
|
+
"i1", actor_id="u1", access_mode="restricted", grants=[]
|
|
909
|
+
)
|
|
910
|
+
assert events == ["parent", "grant_delete"]
|
|
911
|
+
|
|
912
|
+
|
|
883
913
|
# ── Rotation primitives (I-7) ─────────────────────────────────────────────
|
|
884
914
|
|
|
885
915
|
|
|
@@ -1145,7 +1175,10 @@ def test_password_history_snapshot_v1_excludes_plaintext_and_hints(monkeypatch):
|
|
|
1145
1175
|
vault_history.validate_snapshot_v1(snapshot)
|
|
1146
1176
|
|
|
1147
1177
|
|
|
1148
|
-
def _history_update_fakes(
|
|
1178
|
+
def _history_update_fakes(
|
|
1179
|
+
monkeypatch, *, writer_error: Exception | None = None, writer_error_call: int | None = None,
|
|
1180
|
+
revision: int = 0,
|
|
1181
|
+
):
|
|
1149
1182
|
from matrx_orm.secrets_battery import items
|
|
1150
1183
|
|
|
1151
1184
|
old_ciphertext = _history_ciphertext()
|
|
@@ -1179,7 +1212,7 @@ def _history_update_fakes(monkeypatch, *, writer_error: Exception | None = None)
|
|
|
1179
1212
|
|
|
1180
1213
|
async def _writer(**kwargs):
|
|
1181
1214
|
snapshots.append(kwargs["snapshot"])
|
|
1182
|
-
if writer_error:
|
|
1215
|
+
if writer_error and (writer_error_call is None or len(snapshots) == writer_error_call):
|
|
1183
1216
|
raise writer_error
|
|
1184
1217
|
return kwargs["expected_revision"] + 1
|
|
1185
1218
|
|
|
@@ -1189,7 +1222,7 @@ def _history_update_fakes(monkeypatch, *, writer_error: Exception | None = None)
|
|
|
1189
1222
|
monkeypatch.setattr(items, "transaction", _FakeTxn())
|
|
1190
1223
|
monkeypatch.setattr(items, "write_password_field_update_snapshot", _writer)
|
|
1191
1224
|
async def _revision(_item_id):
|
|
1192
|
-
return
|
|
1225
|
+
return revision
|
|
1193
1226
|
monkeypatch.setattr(items, "locked_history_revision", _revision)
|
|
1194
1227
|
monkeypatch.setattr(items, "record_secret_audit", _noop_audit)
|
|
1195
1228
|
return items, updates, snapshots
|
|
@@ -1220,3 +1253,66 @@ async def test_password_update_does_not_mutate_when_history_write_fails(monkeypa
|
|
|
1220
1253
|
audit_organization_id="00000000-0000-0000-0000-000000000003",
|
|
1221
1254
|
)
|
|
1222
1255
|
assert updates == []
|
|
1256
|
+
|
|
1257
|
+
|
|
1258
|
+
def test_password_history_revision_shape_allows_only_one_bootstrap(monkeypatch):
|
|
1259
|
+
from matrx_orm.secrets_battery import vault_history
|
|
1260
|
+
|
|
1261
|
+
monkeypatch.setenv("CREDENTIALS_ENCRYPTION_KEY", _fernet_key())
|
|
1262
|
+
ciphertext = _history_ciphertext()
|
|
1263
|
+
bootstrap = vault_history.password_field_update_snapshot(
|
|
1264
|
+
item_id="00000000-0000-0000-0000-000000000001", item_user_id="00000000-0000-0000-0000-000000000002",
|
|
1265
|
+
audit_organization_id="00000000-0000-0000-0000-000000000003", field_id="00000000-0000-0000-0000-000000000004",
|
|
1266
|
+
field_key="password", handling="revealable", execution_purpose="general", before_encrypted=ciphertext,
|
|
1267
|
+
after_encrypted=ciphertext, capture_started_at=datetime.now(UTC),
|
|
1268
|
+
)
|
|
1269
|
+
vault_history.validate_snapshot_revision(bootstrap, 0)
|
|
1270
|
+
with pytest.raises(vault_history.VaultHistorySnapshotError, match="must be deltas"):
|
|
1271
|
+
vault_history.validate_snapshot_revision(bootstrap, 2)
|
|
1272
|
+
|
|
1273
|
+
|
|
1274
|
+
@pytest.mark.asyncio
|
|
1275
|
+
async def test_password_update_after_bootstrap_writes_one_delta(monkeypatch):
|
|
1276
|
+
monkeypatch.setenv("CREDENTIALS_ENCRYPTION_KEY", _fernet_key())
|
|
1277
|
+
items, _updates, snapshots = _history_update_fakes(monkeypatch, revision=2)
|
|
1278
|
+
await items.update_credential_field_value(
|
|
1279
|
+
"00000000-0000-0000-0000-000000000004", "synthetic-password-c", actor_id="00000000-0000-0000-0000-000000000002",
|
|
1280
|
+
audit_organization_id="00000000-0000-0000-0000-000000000003",
|
|
1281
|
+
)
|
|
1282
|
+
assert len(snapshots) == 1
|
|
1283
|
+
assert snapshots[0]["changed_components"][0]["before"] != snapshots[0]["changed_components"][0]["after"]
|
|
1284
|
+
|
|
1285
|
+
|
|
1286
|
+
@pytest.mark.asyncio
|
|
1287
|
+
async def test_second_history_write_failure_propagates_from_atomic_transaction(monkeypatch):
|
|
1288
|
+
monkeypatch.setenv("CREDENTIALS_ENCRYPTION_KEY", _fernet_key())
|
|
1289
|
+
items, updates, snapshots = _history_update_fakes(
|
|
1290
|
+
monkeypatch, writer_error=RuntimeError("history unavailable"), writer_error_call=2,
|
|
1291
|
+
)
|
|
1292
|
+
with pytest.raises(RuntimeError, match="history unavailable"):
|
|
1293
|
+
await items.update_credential_field_value(
|
|
1294
|
+
"00000000-0000-0000-0000-000000000004", "synthetic-password-b", actor_id="00000000-0000-0000-0000-000000000002",
|
|
1295
|
+
audit_organization_id="00000000-0000-0000-0000-000000000003",
|
|
1296
|
+
)
|
|
1297
|
+
assert len(snapshots) == 2
|
|
1298
|
+
assert updates # The real transaction owns this statement and rolls it back on the propagated failure.
|
|
1299
|
+
|
|
1300
|
+
|
|
1301
|
+
@pytest.mark.asyncio
|
|
1302
|
+
async def test_inactive_password_never_enters_history_capture(monkeypatch):
|
|
1303
|
+
monkeypatch.setenv("CREDENTIALS_ENCRYPTION_KEY", _fernet_key())
|
|
1304
|
+
items, updates, snapshots = _history_update_fakes(monkeypatch)
|
|
1305
|
+
original = items.UserSecret
|
|
1306
|
+
|
|
1307
|
+
class _InactiveSecrets(original):
|
|
1308
|
+
@classmethod
|
|
1309
|
+
def filter(cls, **_kw):
|
|
1310
|
+
return _FakeQB([dict(_FIELD_RAW, id="00000000-0000-0000-0000-000000000004", field_key="password", editable=True, is_active=False, value_encrypted=_history_ciphertext())])
|
|
1311
|
+
|
|
1312
|
+
monkeypatch.setattr(items, "UserSecret", _InactiveSecrets)
|
|
1313
|
+
await items.update_credential_field_value(
|
|
1314
|
+
"00000000-0000-0000-0000-000000000004", "synthetic-password-b", actor_id="00000000-0000-0000-0000-000000000002",
|
|
1315
|
+
audit_organization_id="00000000-0000-0000-0000-000000000003",
|
|
1316
|
+
)
|
|
1317
|
+
assert snapshots == []
|
|
1318
|
+
assert updates
|
matrx_orm-3.1.182/CHANGELOG.md
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|