matrx-orm 3.1.174__tar.gz → 3.1.182__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.174 → matrx_orm-3.1.182}/.gitignore +10 -0
- matrx_orm-3.1.182/CHANGELOG.md +6 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/PKG-INFO +2 -2
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/__init__.py +12 -1
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/catalog_sql.py +40 -0
- matrx_orm-3.1.182/matrx_orm/core/admin_lane.py +47 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/core/expressions.py +9 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/core/fields.py +26 -5
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/core/introspection.py +76 -2
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/core/pooler_role.py +1 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/core/rls_session.py +30 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/migrations/diff.py +15 -0
- matrx_orm-3.1.182/matrx_orm/pytest_live_db_guard.py +190 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/pytest_lock_guard.py +96 -12
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/python_sql/db_objects.py +16 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/python_sql/table_detailed_relationships.py +14 -0
- matrx_orm-3.1.182/matrx_orm/schema_builder/FEATURE.md +42 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/secrets_battery/FEATURE.md +17 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/secrets_battery/__init__.py +17 -0
- matrx_orm-3.1.182/matrx_orm/secrets_battery/backup_restore.py +286 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/secrets_battery/crypto.py +13 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/secrets_battery/items.py +51 -3
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/secrets_battery/mutation_receipts.py +39 -2
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/secrets_battery/org_service.py +7 -4
- matrx_orm-3.1.182/matrx_orm/secrets_battery/vault_history.py +148 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/session/README.md +11 -1
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/session/fallback.py +105 -26
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/session/lifecycle.py +1 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/session/managed.py +5 -2
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/session/session.py +27 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/pyproject.toml +3 -2
- matrx_orm-3.1.182/tests/level1/test_fk_cycle_resolution.py +122 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_from_alias.py +69 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_org_assignment_inventory.py +60 -20
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_rls_session_scope.py +58 -7
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_watched_lifecycle.py +2 -0
- matrx_orm-3.1.182/tests/session/test_a_failed_capture_still_names_the_failure.py +144 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/session/test_flush_individual_fallback.py +34 -0
- matrx_orm-3.1.182/tests/session/test_live_db_guard_plugin.py +103 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/session/test_record_error_bounded_insert.py +22 -2
- matrx_orm-3.1.182/tests/test_backup_alias_conflict.py +44 -0
- matrx_orm-3.1.182/tests/test_backup_restore_live.py +192 -0
- matrx_orm-3.1.182/tests/test_composite_fk_is_not_a_column_fk.py +202 -0
- matrx_orm-3.1.182/tests/test_dsn_password_masking.py +113 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/test_native_passkey_receipts.py +23 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/test_secrets_battery.py +109 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/test_shared_lock_guard.py +77 -13
- matrx_orm-3.1.174/matrx_orm/schema_builder/FEATURE.md +0 -23
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/AGENTS.md +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/BASE_CLASS_METHODS.md +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/CLAUDE.md +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/MODEL_API.md +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/README.md +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/database/orm/extended/managers/ai_model_base.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/docs/migrations.md +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/FEATURE.md +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/MODULE_README.md +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/adapters/MODULE_README.md +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/adapters/__init__.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/adapters/async_postgresql.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/adapters/base_adapter.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/adapters/postgrest_client_adapter.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/adapters/supabase_adapter.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/admin/FEATURE.md +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/admin/__init__.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/admin/router.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/api/__init__.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/api/auth.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/api/config.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/api/handlers.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/api/protocol.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/api/server.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/associations.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/cache_debug.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/catalog.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/client/MODULE_README.md +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/client/__init__.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/client/postgres_connection.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/client/postgrest.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/client/supabase_auth.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/client/supabase_config.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/client/supabase_manager.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/core/EXTENDED-TASK.md +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/core/FEATURE.md +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/core/MODULE_README.md +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/core/RELATIONS-TASKS.md +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/core/TASKS.md +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/core/__init__.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/core/async_db_manager.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/core/base.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/core/config.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/core/connections.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/core/db_function.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/core/diagnostics.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/core/extended.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/core/listen.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/core/loop_filters.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/core/model_dto.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/core/model_view.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/core/one_database.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/core/paginator.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/core/pool_maintenance.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/core/pool_watch.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/core/pydantic_bridge.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/core/registry.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/core/relations.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/core/resilience.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/core/session_context.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/core/signals.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/core/tls.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/core/transaction.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/core/types.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/core/write_queue.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/core/write_retry.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/entity.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/error_handling.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/exceptions.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/extended/__init__.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/extended/app_error_handler.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/identity_rule.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/local_sqlite.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/middleware/__init__.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/middleware/base.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/migrations/MODULE_README.md +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/migrations/__init__.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/migrations/cli.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/migrations/ddl.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/migrations/executor.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/migrations/integration.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/migrations/loader.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/migrations/operations.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/migrations/state.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/migrations/table_filter.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/no_org_assignment.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/operations/FEATURE.md +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/operations/MODULE_README.md +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/operations/__init__.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/operations/bulk_update_values.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/operations/catalog_select.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/operations/conflict.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/operations/conflict_writes.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/operations/create.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/operations/db_functions.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/operations/delete.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/operations/dynamic_admin.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/operations/dynamic_crud.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/operations/graph_walk.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/operations/latest_rows.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/operations/queue_claim.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/operations/read.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/operations/relational_backfill.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/operations/staging_load.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/operations/update.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/platform_access.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/platform_db.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/py.typed +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/pytest_plugin.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/python_sql/MODULE_README.md +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/python_sql/__init__.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/python_sql/table_typescript_relationship.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/query/FEATURE.md +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/query/MODULE_README.md +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/query/__init__.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/query/builder.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/query/executor.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/resources/__init__.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/resources/additional-assigners-2026-09-12.json +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/resources/org_assignment_trigger_baseline.json +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/schema_builder/MODULE_README.md +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/schema_builder/__init__.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/schema_builder/code_handler.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/schema_builder/codegen_writer.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/schema_builder/columns.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/schema_builder/common.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/schema_builder/diff_preview.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/schema_builder/entity_capabilities.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/schema_builder/generator.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/schema_builder/helpers/__init__.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/schema_builder/helpers/base_generators.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/schema_builder/helpers/entity_generators.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/schema_builder/helpers/git_checker.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/schema_builder/package_wiring.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/schema_builder/relationships.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/schema_builder/runner.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/schema_builder/schema.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/schema_builder/schema_manager.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/schema_builder/tables.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/schema_builder/views.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/secrets_battery/attachments.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/secrets_battery/host_authorization.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/secrets_battery/model.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/secrets_battery/native_enable.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/secrets_battery/native_lifecycle.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/secrets_battery/native_passkeys.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/secrets_battery/native_source.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/secrets_battery/service.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/session/__init__.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/session/coalesce.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/session/dag.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/session/errors.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/session/flush.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/session/op.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/session/reads.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/session/telemetry.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/sql_executor/MODULE_README.md +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/sql_executor/__init__.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/sql_executor/executor.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/sql_executor/queries.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/sql_executor/registry.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/sql_executor/types.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/sql_executor/utils.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/state.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/utils/__init__.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/utils/sql_utils.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/utils/type_converters.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/matrx_orm/write_actor.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/scripts/git-branches.sh +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/scripts/publish.sh +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/scripts/release.sh +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/MODULE_README.md +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/__init__.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/conftest.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/MODULE_README.md +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/__init__.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_adapter_registry.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_admin_db_columns.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_admin_response_rendering.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_admin_search_negation.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_admin_search_sentinels.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_advisory_lock.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_agent_message.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_api_auth.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_api_config.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_api_handlers.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_api_protocol.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_array_agg_order_by.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_association_trigger_actor_naming.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_associations.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_associations_wiring.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_bulk_hydration_offload.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_bulk_update_by_pk.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_bulk_upsert_increment_set_fields.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_cache_debug.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_call_function.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_case_when.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_catalog.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_committed_row_guard.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_composite_pk_filter.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_composite_pk_write_paths.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_config.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_conflict_target.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_conflict_writes.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_connection_codecs.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_connection_poison_guard.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_connection_primitives.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_count_composite_distinct.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_count_limit_param_leak.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_db_function_field.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_ddl_generator.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_deadlock_retry.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_derived_table.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_dirty_tracking.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_dynamic_crud.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_exceptions.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_execute_query_integrity_mapping.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_expression_primitives.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_fields.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_filter_jsonb_agg_array_index.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_fts.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_func_expression_args.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_graph_walk.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_having_annotate_alias.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_insert_ignore_and_admin_primitives.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_introspect_rls_policies.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_join_and_aggregate.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_latest_rows.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_listen_notify.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_local_sqlite.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_lock_contention_retry.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_managed_write_guard.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_matrx_entity.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_migration_diff_types.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_migration_loader.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_model_instance.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_model_meta.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_optimistic_concurrency.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_order_by_expression.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_orm_banners_are_client_safe.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_parameter_name_hydration.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_patch_jsonb_path.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_percentile_cont.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_persisted_count.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_pool_holder_census.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_pool_loop_recreation.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_pool_maintenance.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_postgrest_filters.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_pydantic_bridge.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_query_builder.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_query_error_detail.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_query_executor_sql.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_query_timeout.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_querybuilder_clone_semantics.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_queue_claim.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_read_connection_retry.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_registry.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_registry_multi_database.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_registry_multi_schema.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_relational_backfill.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_relations.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_resilience.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_schema_exists.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_session_advisory_lock.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_standalone_write_session_context.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_state_cache.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_subquery_filter_raw.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_subquery_in_filter.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_subquery_nested_expressions.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_supabase_auth.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_supabase_config.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_tls.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_transaction_database_identity.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_typed_join_projection.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_typed_predicates.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_update_case_expression.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_update_subquery.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_update_with_rebase.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_upsert_default_leak.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_upsert_with_conflict.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_values_jsonb_decode.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_write_queue_deadline.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level1/test_write_retry_semantics.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level2/MODULE_README.md +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level2/__init__.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level2/conftest.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level2/test_bulk_ops.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level2/test_cache_integration.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level2/test_crud.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level2/test_foreign_keys.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level2/test_m2m.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level2/test_manager.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level2/test_migrations_live.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level2/test_query_execution.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level2/test_schema_diff.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/level2/verify_committed_row_guard_pg17.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/live_pooler_role.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/native_source_cases.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/native_source_oracle.rs +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/sample_project/.env.example +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/sample_project/README.md +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/sample_project/__init__.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/sample_project/generate.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/sample_project/generated/.gitkeep +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/sample_project/matrx_orm.yaml +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/sample_project/test_schema_generation.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/sample_project_desktop/.env.example +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/sample_project_desktop/README.md +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/sample_project_desktop/__init__.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/sample_project_desktop/client_example.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/sample_project_desktop/client_example.ts +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/sample_project_desktop/client_supabase_example.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/sample_project_desktop/server.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/schema/entity_tests.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/schema/test_base_generation.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/schema/test_composite_pk_fk_generation.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/schema/test_composite_unique_generation.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/schema/test_dto_identity_generation.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/schema/test_entity_capability_generation.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/schema/test_enum_ordering.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/schema/test_generate_schema.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/schema/test_junction_analysis.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/schema/test_multi_schema_output.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/schema/test_schema_analysis.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/schema/test_sql_expression_defaults.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/schema/test_table_relationships_query.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/schema/test_view_generation.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/schema_builder/test_array_default_parsing.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/schema_builder/test_focused_generation_plan.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/schema_builder/test_host_models_aggregator.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/session/__init__.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/session/conftest.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/session/test_cache_no_downgrade.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/session/test_capture_poison_proof.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/session/test_coalesce.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/session/test_dag.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/session/test_disk_spill_fallback.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/session/test_governed_write.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/session/test_managed.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/session/test_op_enqueue_site.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/session/test_reads.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/session/test_record_error_storm.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/session/test_session.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/session/test_spill_guard_plugin.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/test_array_arg_element_type.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/test_attachment_mutation_serialization.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/test_database_name_alias.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/test_db_functions.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/test_dynamic_select_unregistered.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/test_immutable_insert_or_exact_verify.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/test_keyed_digest.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/test_model_cls_refactor.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/test_native_enable.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/test_native_lifecycle.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/test_native_lifecycle_fences.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/test_native_source.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/test_no_blocking_asyncpg_tls.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/test_one_database.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/test_order_by_desc_nulls_last.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/test_pooler_role_guard.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/test_secret_audit_names_its_organization.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/test_secrets_minting_keys.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/test_sql_param_casts.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/test_sync_twins_guard.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/test_user_visible_lookup.py +0 -0
- {matrx_orm-3.1.174 → matrx_orm-3.1.182}/tests/test_vault_aggregate_recovery_stateful.py +0 -0
|
@@ -197,6 +197,14 @@ cython_debug/
|
|
|
197
197
|
# Frontend pnpm workspace (apps/) — node_modules at the workspace root and any
|
|
198
198
|
# member, plus Vite caches and build output. The unified lockfile (apps/pnpm-lock.yaml)
|
|
199
199
|
# IS committed; everything below is regenerated.
|
|
200
|
+
#
|
|
201
|
+
# 🚨 BOTH SPELLINGS. `node_modules/` with the slash matches a DIRECTORY only, so
|
|
202
|
+
# a SYMLINK named `node_modules` slipped straight past it: five of them — each
|
|
203
|
+
# an absolute path into one machine's checkout — were committed on main and
|
|
204
|
+
# broke `pnpm install --frozen-lockfile` on every CI runner, where the symlink
|
|
205
|
+
# is dangling and pnpm's `mkdir` of it answers ENOENT. Every package release was
|
|
206
|
+
# blocked by it. The bare spelling below matches the symlink too.
|
|
207
|
+
node_modules
|
|
200
208
|
node_modules/
|
|
201
209
|
apps/**/.vite/
|
|
202
210
|
apps/**/dist/
|
|
@@ -328,3 +336,5 @@ apps/shared/.kit-*-witness.*/
|
|
|
328
336
|
# They live at .wt/<slug> (disk-groom). Committing the folder would add a
|
|
329
337
|
# second copy of the entire tree and a symlink into common-docs.
|
|
330
338
|
.wt/
|
|
339
|
+
.matrx/fixer-dispatch.json
|
|
340
|
+
.matrx/fixer-ledger.json
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: matrx-orm
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.182
|
|
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.54
|
|
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
|
|
@@ -72,7 +72,13 @@ from .core.diagnostics import (
|
|
|
72
72
|
long_transactions,
|
|
73
73
|
long_transactions_sql,
|
|
74
74
|
)
|
|
75
|
-
from .core.introspection import
|
|
75
|
+
from .core.introspection import (
|
|
76
|
+
introspect_column_shapes,
|
|
77
|
+
introspect_columns,
|
|
78
|
+
introspect_function_privileges,
|
|
79
|
+
introspect_rls_policies,
|
|
80
|
+
schema_exists,
|
|
81
|
+
)
|
|
76
82
|
from .core.one_database import OneDatabaseReport, PoolIdentity, audit_one_database
|
|
77
83
|
from .core.listen import notify, ping
|
|
78
84
|
from .core.tls import resolve_asyncpg_ssl, resolve_asyncpg_ssl_from_dsn
|
|
@@ -249,6 +255,7 @@ from .core.registry import (
|
|
|
249
255
|
validate_relation_resolution,
|
|
250
256
|
)
|
|
251
257
|
from .core.relations import ManyToManyField
|
|
258
|
+
from .core.admin_lane import ADMIN_LANE_GUC, admin_lane
|
|
252
259
|
from .core.rls_session import rls_session
|
|
253
260
|
from .core.session_context import (
|
|
254
261
|
ACTOR_AGENT_GUC,
|
|
@@ -472,7 +479,9 @@ __all__ = [
|
|
|
472
479
|
"audit_one_database",
|
|
473
480
|
"OneDatabaseReport",
|
|
474
481
|
"PoolIdentity",
|
|
482
|
+
"introspect_column_shapes",
|
|
475
483
|
"introspect_columns",
|
|
484
|
+
"introspect_function_privileges",
|
|
476
485
|
"introspect_lock_waits",
|
|
477
486
|
"introspect_rls_policies",
|
|
478
487
|
"schema_exists",
|
|
@@ -709,6 +718,8 @@ __all__ = [
|
|
|
709
718
|
"try_advisory_lock",
|
|
710
719
|
"advisory_unlock",
|
|
711
720
|
"rls_session",
|
|
721
|
+
"admin_lane",
|
|
722
|
+
"ADMIN_LANE_GUC",
|
|
712
723
|
# ── Resilience (opt-in transient-failure retry) ──────────────────────────
|
|
713
724
|
"retry_on_transient",
|
|
714
725
|
"is_read_only_sql",
|
|
@@ -113,3 +113,43 @@ SELECT c.relname AS table_name, a.attname AS column_name
|
|
|
113
113
|
AND NOT a.attisdropped
|
|
114
114
|
AND c.relkind = ANY ('{r,p,v,m,f}')
|
|
115
115
|
"""
|
|
116
|
+
|
|
117
|
+
# The same catalog read, plus the two facts a drift guard actually argues about:
|
|
118
|
+
# whether a column may be empty, and what Postgres puts there when a writer says
|
|
119
|
+
# nothing. A NOT NULL column with a DEFAULT manufactures a value that reads,
|
|
120
|
+
# downstream, exactly like something a person chose — so "is there a default"
|
|
121
|
+
# is a question the ORM must be able to answer without a host hand-rolling SQL.
|
|
122
|
+
COLUMN_SHAPES_FOR_TABLE_SQL = """
|
|
123
|
+
SELECT a.attname AS column_name,
|
|
124
|
+
NOT a.attnotnull AS is_nullable,
|
|
125
|
+
pg_get_expr(d.adbin, d.adrelid) AS column_default
|
|
126
|
+
FROM pg_attribute a
|
|
127
|
+
JOIN pg_class c ON c.oid = a.attrelid
|
|
128
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
129
|
+
LEFT JOIN pg_attrdef d ON d.adrelid = a.attrelid AND d.adnum = a.attnum
|
|
130
|
+
WHERE n.nspname = $1
|
|
131
|
+
AND c.relname = $2
|
|
132
|
+
AND a.attnum > 0
|
|
133
|
+
AND NOT a.attisdropped
|
|
134
|
+
AND c.relkind = ANY ('{r,p,v,m,f}')
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
# "Who may CALL this function?" is the other half of the definer question. A
|
|
139
|
+
# SECURITY DEFINER function runs as the owner with BYPASSRLS, so the set of
|
|
140
|
+
# roles holding EXECUTE on it IS its access control — and a grant that quietly
|
|
141
|
+
# includes `anon` or `authenticated` hands a browser the owner's privileges.
|
|
142
|
+
# Like COLUMN_SHAPES_FOR_TABLE_SQL, this is a catalog question with no Model to
|
|
143
|
+
# bind it to, so the ORM answers it rather than every guard hand-rolling SQL.
|
|
144
|
+
FUNCTION_PRIVILEGES_SQL = """
|
|
145
|
+
SELECT p.oid::regprocedure::text AS signature,
|
|
146
|
+
p.prosecdef AS is_security_definer,
|
|
147
|
+
role_name AS role,
|
|
148
|
+
has_function_privilege(role_name, p.oid, 'EXECUTE') AS can_execute
|
|
149
|
+
FROM pg_proc p
|
|
150
|
+
JOIN pg_namespace n ON n.oid = p.pronamespace
|
|
151
|
+
CROSS JOIN unnest($3::text[]) AS role_name
|
|
152
|
+
WHERE n.nspname = $1
|
|
153
|
+
AND p.proname = $2
|
|
154
|
+
AND EXISTS (SELECT 1 FROM pg_roles r WHERE r.rolname = role_name)
|
|
155
|
+
"""
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""THE ADMIN LANE — the one door that lets a platform admin's database powers work.
|
|
2
|
+
|
|
3
|
+
Our database's admin arms (``is_platform_admin()``, ``is_super_admin()``, ``is_admin()``,
|
|
4
|
+
``is_super_admin_for(uuid)``, ``is_platform_admin_for(uuid)`` and the kernel's admin rung)
|
|
5
|
+
answer yes only while ``platform.admin_lane_open()`` is true: the request carried the
|
|
6
|
+
admin-app lane header, or the current TRANSACTION set ``matrx.admin_lane`` to ``on``.
|
|
7
|
+
A process on a pooled connection never forwards request headers, so on the server the
|
|
8
|
+
lane is closed unless code opens it here.
|
|
9
|
+
|
|
10
|
+
Law (Arman, 2026-09-25): an admin on a normal user page sees nothing more than anyone
|
|
11
|
+
else; admin powers work only in the admin sections. The lane opens ARMS; it grants
|
|
12
|
+
nothing to a non-admin — the database still checks identity. It is transaction-local,
|
|
13
|
+
so it can never survive onto another client's pooled connection.
|
|
14
|
+
|
|
15
|
+
Only admin surfaces may open it (host guard: ``scripts/check_admin_lane_scope.py``).
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from contextlib import asynccontextmanager
|
|
21
|
+
from typing import AsyncIterator
|
|
22
|
+
|
|
23
|
+
from matrx_orm.core.transaction import get_active_connection, set_local_guc, transaction
|
|
24
|
+
|
|
25
|
+
#: The transaction-local GUC ``platform.admin_lane_open()`` reads. The database names
|
|
26
|
+
#: it; a constant, never configuration — a drift would silently close every lane.
|
|
27
|
+
ADMIN_LANE_GUC = "matrx.admin_lane"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@asynccontextmanager
|
|
31
|
+
async def admin_lane(*, database: str) -> AsyncIterator[None]:
|
|
32
|
+
"""Open the admin lane for every database call inside the block.
|
|
33
|
+
|
|
34
|
+
Inside an open transaction (``rls_session`` / ``transaction()``) the lane is switched
|
|
35
|
+
on for THAT transaction; otherwise a transaction is opened so the setting and the
|
|
36
|
+
work share one connection. Either way it resets when the transaction ends.
|
|
37
|
+
"""
|
|
38
|
+
if get_active_connection() is not None:
|
|
39
|
+
await set_local_guc(ADMIN_LANE_GUC, "on")
|
|
40
|
+
yield
|
|
41
|
+
return
|
|
42
|
+
async with transaction(database=database):
|
|
43
|
+
await set_local_guc(ADMIN_LANE_GUC, "on")
|
|
44
|
+
yield
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
__all__ = ["ADMIN_LANE_GUC", "admin_lane"]
|
|
@@ -1081,7 +1081,16 @@ def _render_subquery(
|
|
|
1081
1081
|
else:
|
|
1082
1082
|
join_clauses.append(f"{j_type} JOIN {j_table} ON {j_on_sql}")
|
|
1083
1083
|
|
|
1084
|
+
# ``from_alias`` is the name every inner column and every nested OuterRef was
|
|
1085
|
+
# just resolved against (``current_table`` above), so the FROM must DECLARE it —
|
|
1086
|
+
# the same contract the top-level executor keeps. Rendering the alias for
|
|
1087
|
+
# resolution but not in FROM produced "missing FROM-clause entry for table
|
|
1088
|
+
# <alias>" on any nested Exists that correlated to an aliased subquery
|
|
1089
|
+
# (search lab inventory, 2026-09-25).
|
|
1090
|
+
from_alias = query_dict.get("from_alias")
|
|
1084
1091
|
sql = f"SELECT {select_clause} FROM {table}"
|
|
1092
|
+
if from_alias and from_alias != table:
|
|
1093
|
+
sql += f" AS {from_alias}"
|
|
1085
1094
|
if join_clauses:
|
|
1086
1095
|
sql += " " + " ".join(join_clauses)
|
|
1087
1096
|
if where_conditions:
|
|
@@ -1596,18 +1596,39 @@ class ForeignKey(Field):
|
|
|
1596
1596
|
self.db_type = self._target_field.db_type
|
|
1597
1597
|
return self._target_field
|
|
1598
1598
|
|
|
1599
|
+
def _terminal_field(self) -> Field | None:
|
|
1600
|
+
"""The concrete (non-FK) column at the end of this FK's target chain.
|
|
1601
|
+
|
|
1602
|
+
A target column may itself be a ForeignKey, so the chain is followed hop
|
|
1603
|
+
by hop with a visited set. A chain that loops back on itself — a real
|
|
1604
|
+
schema shape: ``content.document.id`` -> ``content.document_version.
|
|
1605
|
+
document_id`` -> ``content.document.id`` — has no concrete column to
|
|
1606
|
+
mirror, so it terminates at ``None`` and every caller takes the same
|
|
1607
|
+
documented UUID fallback an unresolvable target takes. Without the
|
|
1608
|
+
visited set every type read on such a column recursed forever.
|
|
1609
|
+
"""
|
|
1610
|
+
visited: set[int] = {id(self)}
|
|
1611
|
+
current: Field = self
|
|
1612
|
+
while isinstance(current, ForeignKey):
|
|
1613
|
+
target = current._get_target_field()
|
|
1614
|
+
if target is None or id(target) in visited:
|
|
1615
|
+
return None
|
|
1616
|
+
visited.add(id(target))
|
|
1617
|
+
current = target
|
|
1618
|
+
return current
|
|
1619
|
+
|
|
1599
1620
|
def python_type(self) -> type:
|
|
1600
|
-
target = self.
|
|
1621
|
+
target = self._terminal_field()
|
|
1601
1622
|
return target.python_type() if target is not None else str
|
|
1602
1623
|
|
|
1603
1624
|
def to_python(self, value):
|
|
1604
|
-
target = self.
|
|
1625
|
+
target = self._terminal_field()
|
|
1605
1626
|
if target is not None:
|
|
1606
1627
|
return target.to_python(value)
|
|
1607
1628
|
return str(value) if value else None
|
|
1608
1629
|
|
|
1609
1630
|
def from_db_value(self, value):
|
|
1610
|
-
target = self.
|
|
1631
|
+
target = self._terminal_field()
|
|
1611
1632
|
if target is not None:
|
|
1612
1633
|
return target.from_db_value(value)
|
|
1613
1634
|
return str(value) if value else None
|
|
@@ -1616,7 +1637,7 @@ class ForeignKey(Field):
|
|
|
1616
1637
|
if value is None:
|
|
1617
1638
|
return None
|
|
1618
1639
|
_reject_blank_uuid(self.name, value, "ForeignKey")
|
|
1619
|
-
target = self.
|
|
1640
|
+
target = self._terminal_field()
|
|
1620
1641
|
if target is not None:
|
|
1621
1642
|
return target.get_db_prep_value(value)
|
|
1622
1643
|
return str(value)
|
|
@@ -1625,7 +1646,7 @@ class ForeignKey(Field):
|
|
|
1625
1646
|
await super().validate(value)
|
|
1626
1647
|
if value is None:
|
|
1627
1648
|
return
|
|
1628
|
-
target = self.
|
|
1649
|
+
target = self._terminal_field()
|
|
1629
1650
|
if target is None:
|
|
1630
1651
|
# No resolved target — keep the historical UUID format check.
|
|
1631
1652
|
try:
|
|
@@ -17,11 +17,21 @@ from __future__ import annotations
|
|
|
17
17
|
|
|
18
18
|
from typing import Any
|
|
19
19
|
|
|
20
|
-
from matrx_orm.catalog_sql import
|
|
20
|
+
from matrx_orm.catalog_sql import (
|
|
21
|
+
COLUMN_SHAPES_FOR_TABLE_SQL,
|
|
22
|
+
COLUMNS_FOR_TABLES_SQL,
|
|
23
|
+
FUNCTION_PRIVILEGES_SQL,
|
|
24
|
+
SCHEMA_EXISTS_SQL,
|
|
25
|
+
)
|
|
21
26
|
from matrx_orm.core.async_db_manager import AsyncDatabaseManager
|
|
22
27
|
from matrx_orm.core.transaction import get_active_connection
|
|
23
28
|
|
|
24
|
-
__all__ = [
|
|
29
|
+
__all__ = [
|
|
30
|
+
"introspect_function_privileges",
|
|
31
|
+
"introspect_rls_policies",
|
|
32
|
+
"introspect_columns",
|
|
33
|
+
"schema_exists",
|
|
34
|
+
]
|
|
25
35
|
|
|
26
36
|
_RLS_AUDIT_SQL = """
|
|
27
37
|
select
|
|
@@ -77,6 +87,7 @@ async def introspect_rls_policies(
|
|
|
77
87
|
# we assume, present columns read as absent and the drift guard reports the code
|
|
78
88
|
# as wrong about the schema. See matrx_orm/catalog_sql.py.
|
|
79
89
|
_COLUMNS_SQL = COLUMNS_FOR_TABLES_SQL
|
|
90
|
+
_COLUMN_SHAPES_SQL = COLUMN_SHAPES_FOR_TABLE_SQL
|
|
80
91
|
|
|
81
92
|
|
|
82
93
|
async def introspect_columns(database: str, schema: str, tables: list[str]) -> dict[str, list[str]]:
|
|
@@ -120,3 +131,66 @@ async def schema_exists(database: str, schema: str) -> bool:
|
|
|
120
131
|
|
|
121
132
|
async with AsyncDatabaseManager.get_connection(database) as conn:
|
|
122
133
|
return bool(await conn.fetchval(sql, schema))
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
async def introspect_column_shapes(
|
|
137
|
+
database: str, schema: str, table: str
|
|
138
|
+
) -> dict[str, dict[str, object]]:
|
|
139
|
+
"""Live nullability and default per column — ``{column: {...}}``.
|
|
140
|
+
|
|
141
|
+
``introspect_columns`` answers "does this column exist"; this answers "what
|
|
142
|
+
will Postgres do when a writer says nothing about it". A NOT NULL column
|
|
143
|
+
with a DEFAULT does not leave a hole — it FILLS one, and every reader
|
|
144
|
+
downstream sees a value indistinguishable from a deliberate choice. On
|
|
145
|
+
2026-09-22 that shape on one column
|
|
146
|
+
(``communication.sms_notification_preferences.timezone``) made an entire
|
|
147
|
+
feature a no-op in production. A guard must be able to see it without
|
|
148
|
+
hand-rolling SQL.
|
|
149
|
+
|
|
150
|
+
Each value is ``{"is_nullable": bool, "column_default": str | None}``.
|
|
151
|
+
``schema``/``table`` are bound as parameters, never interpolated.
|
|
152
|
+
"""
|
|
153
|
+
rows = await AsyncDatabaseManager.execute_query(database, _COLUMN_SHAPES_SQL, schema, table)
|
|
154
|
+
return {
|
|
155
|
+
str(r["column_name"]): {
|
|
156
|
+
"is_nullable": bool(r["is_nullable"]),
|
|
157
|
+
"column_default": (
|
|
158
|
+
str(r["column_default"]) if r["column_default"] is not None else None
|
|
159
|
+
),
|
|
160
|
+
}
|
|
161
|
+
for r in rows
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
async def introspect_function_privileges(
|
|
166
|
+
database: str, schema: str, name: str, roles: list[str]
|
|
167
|
+
) -> list[dict[str, Any]]:
|
|
168
|
+
"""Which of ``roles`` can EXECUTE ``schema.name`` — one row per overload/role.
|
|
169
|
+
|
|
170
|
+
``introspect_column_shapes`` answers "what will Postgres do when a writer
|
|
171
|
+
says nothing"; this answers "who can call this door". A SECURITY DEFINER
|
|
172
|
+
function runs as its owner with BYPASSRLS, so the roles holding EXECUTE on
|
|
173
|
+
it ARE its access control: if ``anon`` or ``authenticated`` is among them,
|
|
174
|
+
a browser holds the owner's privileges. A guard has to be able to see that
|
|
175
|
+
without hand-rolling a catalog query, which is why this lives here.
|
|
176
|
+
|
|
177
|
+
Each row is ``{"signature": str, "is_security_definer": bool, "role": str,
|
|
178
|
+
"can_execute": bool}``. An overload that exists yields one row per role in
|
|
179
|
+
``roles``; a function that does not exist yields no rows at all, which is
|
|
180
|
+
how a caller tells "missing" from "locked". A name in ``roles`` that is not
|
|
181
|
+
a role on this server is skipped rather than raising — a server without a
|
|
182
|
+
``supabase_auth_admin`` is a different deployment, not a violation.
|
|
183
|
+
``schema``, ``name`` and ``roles`` are bound parameters, never interpolated.
|
|
184
|
+
"""
|
|
185
|
+
rows = await AsyncDatabaseManager.execute_query(
|
|
186
|
+
database, FUNCTION_PRIVILEGES_SQL, schema, name, list(roles)
|
|
187
|
+
)
|
|
188
|
+
return [
|
|
189
|
+
{
|
|
190
|
+
"signature": str(r["signature"]),
|
|
191
|
+
"is_security_definer": bool(r["is_security_definer"]),
|
|
192
|
+
"role": str(r["role"]),
|
|
193
|
+
"can_execute": bool(r["can_execute"]),
|
|
194
|
+
}
|
|
195
|
+
for r in rows
|
|
196
|
+
]
|
|
@@ -203,6 +203,7 @@ async def emit_contamination(event: EmptyReadContaminationEvent) -> None:
|
|
|
203
203
|
),
|
|
204
204
|
kind="pooler_role_contamination",
|
|
205
205
|
source_app="matrx-orm",
|
|
206
|
+
source_feature="system",
|
|
206
207
|
route=f"orm:{event.config_name}",
|
|
207
208
|
payload={
|
|
208
209
|
"config_name": event.config_name,
|
|
@@ -51,6 +51,23 @@ _ROLE_RE = re.compile(r"^[a-z_][a-z0-9_]*$")
|
|
|
51
51
|
# agent query holding a pooled connection. Callers may override or disable (None).
|
|
52
52
|
DEFAULT_STATEMENT_TIMEOUT_MS = 30_000
|
|
53
53
|
|
|
54
|
+
# Default ceiling on how long an RLS session may sit IDLE INSIDE ITS TRANSACTION before
|
|
55
|
+
# Postgres ends it. Measured on the main database 2026-09-20, lane TIDY: one backend sat
|
|
56
|
+
# `idle in transaction` on `Client/ClientRead` for 89.7 seconds holding AccessShareLock on
|
|
57
|
+
# every partition and index of `custom.record`, `custom.merge_field_provenance` and
|
|
58
|
+
# `custom.anon_form`. A campaign suite then asked for AccessExclusiveLock on one of those
|
|
59
|
+
# relations and queued behind it — and because Postgres's lock queue is FIFO, every
|
|
60
|
+
# ordinary reader that arrived after that queued behind the DDL, four backends deep within
|
|
61
|
+
# seconds, none of which conflicted with the lock actually being held.
|
|
62
|
+
#
|
|
63
|
+
# Nothing legitimate is idle inside a transaction for thirty seconds: the whole point of
|
|
64
|
+
# holding a session open for a turn is that work is happening in it. What IS idle that long
|
|
65
|
+
# is a turn whose caller is waiting on a model, a network hop or a person — and a
|
|
66
|
+
# connection holding store locks while it waits is a stall for everybody else. This ends
|
|
67
|
+
# such a session instead, releasing every lock; the caller sees a named error rather than
|
|
68
|
+
# the rest of the platform seeing a mystery.
|
|
69
|
+
DEFAULT_IDLE_IN_TRANSACTION_TIMEOUT_MS = 30_000
|
|
70
|
+
|
|
54
71
|
|
|
55
72
|
@asynccontextmanager
|
|
56
73
|
async def rls_session(
|
|
@@ -60,6 +77,7 @@ async def rls_session(
|
|
|
60
77
|
database: str | None = None,
|
|
61
78
|
statement_timeout_ms: int | None = DEFAULT_STATEMENT_TIMEOUT_MS,
|
|
62
79
|
lock_timeout_ms: int | None = None,
|
|
80
|
+
idle_in_transaction_timeout_ms: int | None = DEFAULT_IDLE_IN_TRANSACTION_TIMEOUT_MS,
|
|
63
81
|
) -> AsyncIterator[None]:
|
|
64
82
|
"""Run the enclosed block as ``role`` with ``request.jwt.claims`` set to ``claims``.
|
|
65
83
|
|
|
@@ -72,6 +90,13 @@ async def rls_session(
|
|
|
72
90
|
``statement_timeout`` does not bound it, and an unbounded waiter queues behind the
|
|
73
91
|
dump or sweep that is already running while blocking everyone behind it. Like the
|
|
74
92
|
role and the claims it is ``SET LOCAL`` — it cannot outlive the transaction.
|
|
93
|
+
|
|
94
|
+
``idle_in_transaction_timeout_ms`` bounds how long this session may sit idle INSIDE
|
|
95
|
+
its transaction while holding every lock it has taken. It rides the same one round
|
|
96
|
+
trip as the other three settings, so it is free, and it is what stops one waiting
|
|
97
|
+
caller becoming a stall for every reader of the relations it touched — see
|
|
98
|
+
:data:`DEFAULT_IDLE_IN_TRANSACTION_TIMEOUT_MS` for the 89.7-second case that put it
|
|
99
|
+
here. ``None`` leaves the server default alone.
|
|
75
100
|
"""
|
|
76
101
|
if not _ROLE_RE.match(role):
|
|
77
102
|
raise DatabaseError(
|
|
@@ -119,6 +144,11 @@ async def rls_session(
|
|
|
119
144
|
if lock_timeout_ms is not None:
|
|
120
145
|
bound.append(str(int(lock_timeout_ms)))
|
|
121
146
|
settings.append(f"set_config('lock_timeout', ${len(bound)}, true)")
|
|
147
|
+
if idle_in_transaction_timeout_ms is not None:
|
|
148
|
+
bound.append(str(int(idle_in_transaction_timeout_ms)))
|
|
149
|
+
settings.append(
|
|
150
|
+
f"set_config('idle_in_transaction_session_timeout', ${len(bound)}, true)"
|
|
151
|
+
)
|
|
122
152
|
bound.append(role)
|
|
123
153
|
settings.append(f"set_config('role', ${len(bound)}, true)")
|
|
124
154
|
await conn.execute("select " + ", ".join(settings), *bound)
|
|
@@ -236,6 +236,21 @@ SELECT
|
|
|
236
236
|
WHERE fk.conrelid = a.attrelid
|
|
237
237
|
AND fk.contype = 'f'
|
|
238
238
|
AND array_position(fk.conkey, a.attnum) IS NOT NULL
|
|
239
|
+
-- The SAME rule the model generator uses (python_sql/db_objects.py): a
|
|
240
|
+
-- composite FK's column is a reference only when its target column is
|
|
241
|
+
-- unique alone. Diverging would make the diff propose dropping an FK
|
|
242
|
+
-- the models "lost" under a constraint name that does not exist.
|
|
243
|
+
AND (
|
|
244
|
+
cardinality(fk.conkey) = 1
|
|
245
|
+
OR EXISTS (
|
|
246
|
+
SELECT 1 FROM pg_index ri
|
|
247
|
+
WHERE ri.indrelid = fk.confrelid
|
|
248
|
+
AND ri.indisunique AND ri.indpred IS NULL
|
|
249
|
+
AND ri.indnkeyatts = 1
|
|
250
|
+
AND ri.indkey[0] = fk.confkey[array_position(fk.conkey, a.attnum)]
|
|
251
|
+
)
|
|
252
|
+
)
|
|
253
|
+
ORDER BY cardinality(fk.conkey), fk.conname
|
|
239
254
|
LIMIT 1
|
|
240
255
|
) AS fk_ref
|
|
241
256
|
FROM pg_attribute a
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"""Session-wide guard: a test run never reaches the LIVE platform database by accident.
|
|
2
|
+
|
|
3
|
+
Registered as a ``pytest11`` entry point, so pytest loads it in every suite regardless of
|
|
4
|
+
rootdir — the same mechanism as the spill guard (``matrx_orm.pytest_plugin``) and the lock
|
|
5
|
+
guard (``matrx_orm.pytest_lock_guard``). This is the SEVENTH cross-suite hazard; the other six
|
|
6
|
+
are catalogued in the aidream repo-root ``conftest.py``.
|
|
7
|
+
|
|
8
|
+
WHY IT EXISTS (lane INTEG-SERVER, 2026-09-24). A developer's ``.env`` holds the five
|
|
9
|
+
``SUPABASE_MATRIX_*`` variables for the LIVE database, because that is what the server runs
|
|
10
|
+
on. Those variables SELECT a connection; they never AUTHORISED one. So every suite that
|
|
11
|
+
imports the host's ``db`` package — ``aidream/services/agent_data/tests/test_rls_data_access.py``
|
|
12
|
+
creates, edits and HARD-deletes notes — ran against production whenever it was collected,
|
|
13
|
+
with nobody having decided that. The matrx-records suite closed this for itself on 2026-09-22
|
|
14
|
+
(a declared target, default the rehearsal branch, main behind three locked doors); this closes
|
|
15
|
+
it for every suite, once, in the package that owns the connection.
|
|
16
|
+
|
|
17
|
+
THE RULE. Before any test module is imported, the connection the five variables (or the
|
|
18
|
+
``.env`` that will supply them) resolve to is compared with the live database's identity —
|
|
19
|
+
its project ref in the pooler user (``postgres.<ref>``) or a live host. When it IS the live
|
|
20
|
+
database:
|
|
21
|
+
|
|
22
|
+
* **Default:** the five variables are REPOINTED, in this process only, to the rehearsal
|
|
23
|
+
branch — ``MATRX_TEST_DATABASE_URL`` when set (any declared non-live target, e.g. the dev
|
|
24
|
+
clone), else ``SUPABASE_BRANCH_DATABASE_URL``. With neither, they point at an unreachable
|
|
25
|
+
placeholder (``127.0.0.1:1``), so a live test FAILS to connect instead of writing to
|
|
26
|
+
production. The banner says which, once.
|
|
27
|
+
* **Live, on purpose:** ``--live-db --live-db-reason "<why>"`` inside the 1–4 AM Pacific
|
|
28
|
+
maintenance window leaves them alone and prints the reason. The environment cannot open
|
|
29
|
+
this door (an exported variable outlives the shell that set it); a missing reason or a
|
|
30
|
+
time outside the window is a usage error that stops the session.
|
|
31
|
+
|
|
32
|
+
A connection that is NOT the live database — a local Postgres in CI, the branch, the clone —
|
|
33
|
+
is never touched. Values are never printed.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
from __future__ import annotations
|
|
37
|
+
|
|
38
|
+
import os
|
|
39
|
+
from datetime import datetime
|
|
40
|
+
from urllib.parse import unquote, urlsplit
|
|
41
|
+
from zoneinfo import ZoneInfo
|
|
42
|
+
|
|
43
|
+
import pytest
|
|
44
|
+
|
|
45
|
+
#: The ONE live database (aidream CLAUDE.md), by the identities a connection can carry.
|
|
46
|
+
LIVE_PROJECT_REFS = frozenset({"brsgrqvjdzwihsvnfqkf"})
|
|
47
|
+
LIVE_HOSTS = frozenset({"db.matrxserver.com", "db.brsgrqvjdzwihsvnfqkf.supabase.co"})
|
|
48
|
+
|
|
49
|
+
DB_VARS = (
|
|
50
|
+
"SUPABASE_MATRIX_HOST",
|
|
51
|
+
"SUPABASE_MATRIX_PORT",
|
|
52
|
+
"SUPABASE_MATRIX_USER",
|
|
53
|
+
"SUPABASE_MATRIX_PASSWORD",
|
|
54
|
+
"SUPABASE_MATRIX_DATABASE_NAME",
|
|
55
|
+
)
|
|
56
|
+
TARGET_URL_VARS = ("MATRX_TEST_DATABASE_URL", "SUPABASE_BRANCH_DATABASE_URL")
|
|
57
|
+
WINDOW_TZ, WINDOW_START, WINDOW_END = "America/Los_Angeles", 1, 4
|
|
58
|
+
PLACEHOLDER = {
|
|
59
|
+
"SUPABASE_MATRIX_HOST": "127.0.0.1",
|
|
60
|
+
"SUPABASE_MATRIX_PORT": "1",
|
|
61
|
+
"SUPABASE_MATRIX_USER": "live-db-refused-under-pytest",
|
|
62
|
+
"SUPABASE_MATRIX_PASSWORD": "refused",
|
|
63
|
+
"SUPABASE_MATRIX_DATABASE_NAME": "refused",
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
#: The ONLY accounts a test may act as (workspace law: "use ONLY admin@admin.com, never Arman's
|
|
67
|
+
#: personal account"; test@test.com is the non-admin seat). By id, because an id is what a test
|
|
68
|
+
#: reads from ``TEST_USER_ID`` and an id cannot be mistaken for a similar email.
|
|
69
|
+
TEST_ACCOUNT_IDS = {
|
|
70
|
+
"87a6e699-3622-4869-8843-d0867456c0dd": "admin@admin.com",
|
|
71
|
+
"4060701e-706a-4c76-b3ca-0bbc69fa5a14": "test@test.com",
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def refuse_a_real_person(env: dict[str, str], dotenv: dict[str, str]) -> None:
|
|
76
|
+
"""``TEST_USER_ID`` must be one of the test accounts, or the session does not start."""
|
|
77
|
+
user = (env.get("TEST_USER_ID") or dotenv.get("TEST_USER_ID") or "").strip()
|
|
78
|
+
if user and user not in TEST_ACCOUNT_IDS:
|
|
79
|
+
raise pytest.UsageError(
|
|
80
|
+
"REFUSED: TEST_USER_ID is not a test account. Tests act ONLY as admin@admin.com "
|
|
81
|
+
"(or test@test.com for a non-admin seat) — never a real person's account, and never "
|
|
82
|
+
"the owner's personal one. Set TEST_USER_ID to admin@admin.com's user id. Nothing ran."
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
#: What this session decided, for tests and banners: "untouched" | "live" | "repointed:<var>"
|
|
87
|
+
#: | "placeholder".
|
|
88
|
+
DECISION: str = "untouched"
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def is_live(user: str, host: str) -> bool:
|
|
92
|
+
"""Does this connection identity name the live database?"""
|
|
93
|
+
ref = user.split(".", 1)[1] if "." in (user or "") else ""
|
|
94
|
+
return ref in LIVE_PROJECT_REFS or (host or "").strip().lower() in LIVE_HOSTS
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _dotenv_values() -> dict[str, str]:
|
|
98
|
+
"""The ``.env`` a later ``load_dotenv()`` would read — it never overrides what is set here."""
|
|
99
|
+
try:
|
|
100
|
+
from dotenv import dotenv_values, find_dotenv
|
|
101
|
+
except ImportError: # pragma: no cover — python-dotenv absent: env only
|
|
102
|
+
return {}
|
|
103
|
+
path = find_dotenv(usecwd=True)
|
|
104
|
+
return {k: v for k, v in (dotenv_values(path) if path else {}).items() if v is not None}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _five_from_url(url: str) -> dict[str, str]:
|
|
108
|
+
parts = urlsplit(url)
|
|
109
|
+
return {
|
|
110
|
+
"SUPABASE_MATRIX_HOST": parts.hostname or "",
|
|
111
|
+
"SUPABASE_MATRIX_PORT": str(parts.port or 5432),
|
|
112
|
+
"SUPABASE_MATRIX_USER": unquote(parts.username or ""),
|
|
113
|
+
"SUPABASE_MATRIX_PASSWORD": unquote(parts.password or ""),
|
|
114
|
+
"SUPABASE_MATRIX_DATABASE_NAME": (parts.path or "/postgres").lstrip("/") or "postgres",
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def decide(
|
|
119
|
+
env: dict[str, str],
|
|
120
|
+
dotenv: dict[str, str],
|
|
121
|
+
*,
|
|
122
|
+
live_flag: bool,
|
|
123
|
+
reason: str,
|
|
124
|
+
now: datetime,
|
|
125
|
+
) -> tuple[str, dict[str, str] | None, str]:
|
|
126
|
+
"""``(decision, five variables to set or None, banner)``. Pure — the hook applies it."""
|
|
127
|
+
effective = {k: env.get(k) or dotenv.get(k, "") for k in DB_VARS}
|
|
128
|
+
if not is_live(effective["SUPABASE_MATRIX_USER"], effective["SUPABASE_MATRIX_HOST"]):
|
|
129
|
+
return "untouched", None, ""
|
|
130
|
+
if live_flag:
|
|
131
|
+
if not reason.strip():
|
|
132
|
+
raise pytest.UsageError(
|
|
133
|
+
"--live-db needs --live-db-reason \"<why>\": nothing else records why a test run "
|
|
134
|
+
"touched the live database."
|
|
135
|
+
)
|
|
136
|
+
local = now.astimezone(ZoneInfo(WINDOW_TZ))
|
|
137
|
+
if not (WINDOW_START <= local.hour < WINDOW_END):
|
|
138
|
+
raise pytest.UsageError(
|
|
139
|
+
f"--live-db refused: it is {local:%H:%M} Pacific, and the live database is open "
|
|
140
|
+
f"to test runs only {WINDOW_START:02d}:00–{WINDOW_END:02d}:00 Pacific."
|
|
141
|
+
)
|
|
142
|
+
return "live", None, f"── LIVE DATABASE under pytest · reason: {reason.strip()}"
|
|
143
|
+
for var in TARGET_URL_VARS:
|
|
144
|
+
url = env.get(var) or dotenv.get(var, "")
|
|
145
|
+
if url:
|
|
146
|
+
five = _five_from_url(url)
|
|
147
|
+
if is_live(five["SUPABASE_MATRIX_USER"], five["SUPABASE_MATRIX_HOST"]):
|
|
148
|
+
raise pytest.UsageError(f"{var} names the LIVE database; it cannot be a test target.")
|
|
149
|
+
return (
|
|
150
|
+
f"repointed:{var}",
|
|
151
|
+
five,
|
|
152
|
+
f"── the live database was configured; this test run uses {var} instead "
|
|
153
|
+
"(pass --live-db --live-db-reason inside 1–4 AM Pacific to reach live on purpose).",
|
|
154
|
+
)
|
|
155
|
+
return (
|
|
156
|
+
"placeholder",
|
|
157
|
+
dict(PLACEHOLDER),
|
|
158
|
+
"── the live database was configured and no test target is set "
|
|
159
|
+
"(MATRX_TEST_DATABASE_URL / SUPABASE_BRANCH_DATABASE_URL); database connections in this "
|
|
160
|
+
"run go to an unreachable placeholder and FAIL instead of reaching production.",
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def pytest_addoption(parser: pytest.Parser) -> None:
|
|
165
|
+
group = parser.getgroup("matrx-live-db", "the live platform database under pytest")
|
|
166
|
+
group.addoption("--live-db", action="store_true", default=False,
|
|
167
|
+
help="reach the LIVE database on purpose (needs --live-db-reason and 1-4 AM Pacific)")
|
|
168
|
+
group.addoption("--live-db-reason", action="store", default="",
|
|
169
|
+
help="why this run needs the live database")
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@pytest.hookimpl(tryfirst=True)
|
|
173
|
+
def pytest_configure(config: pytest.Config) -> None:
|
|
174
|
+
global DECISION
|
|
175
|
+
refuse_a_real_person(dict(os.environ), _dotenv_values())
|
|
176
|
+
decision, five, banner = decide(
|
|
177
|
+
dict(os.environ),
|
|
178
|
+
_dotenv_values(),
|
|
179
|
+
live_flag=bool(config.getoption("--live-db", default=False)),
|
|
180
|
+
reason=str(config.getoption("--live-db-reason", default="") or ""),
|
|
181
|
+
now=datetime.now(ZoneInfo(WINDOW_TZ)),
|
|
182
|
+
)
|
|
183
|
+
DECISION = decision
|
|
184
|
+
if five is not None:
|
|
185
|
+
os.environ.update(five)
|
|
186
|
+
if banner:
|
|
187
|
+
print(banner)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
__all__ = ["DECISION", "TEST_ACCOUNT_IDS", "refuse_a_real_person", "LIVE_HOSTS", "LIVE_PROJECT_REFS", "decide", "is_live"]
|