matrx-orm 3.1.183__tar.gz → 3.1.185__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 → matrx_orm-3.1.185}/PKG-INFO +2 -2
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/secrets_battery/backup_restore.py +143 -1
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/secrets_battery/items.py +141 -40
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/secrets_battery/vault_history.py +25 -2
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/pyproject.toml +2 -2
- matrx_orm-3.1.185/tests/test_backup_history_restore.py +144 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/test_backup_restore_live.py +28 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/test_secrets_battery.py +129 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/.gitignore +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/AGENTS.md +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/BASE_CLASS_METHODS.md +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/CHANGELOG.md +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/CLAUDE.md +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/MODEL_API.md +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/README.md +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/database/orm/extended/managers/ai_model_base.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/docs/migrations.md +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/FEATURE.md +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/MODULE_README.md +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/__init__.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/adapters/MODULE_README.md +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/adapters/__init__.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/adapters/async_postgresql.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/adapters/base_adapter.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/adapters/postgrest_client_adapter.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/adapters/supabase_adapter.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/admin/FEATURE.md +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/admin/__init__.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/admin/router.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/api/__init__.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/api/auth.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/api/config.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/api/handlers.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/api/protocol.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/api/server.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/associations.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/cache_debug.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/catalog.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/catalog_sql.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/client/MODULE_README.md +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/client/__init__.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/client/postgres_connection.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/client/postgrest.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/client/supabase_auth.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/client/supabase_config.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/client/supabase_manager.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/core/EXTENDED-TASK.md +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/core/FEATURE.md +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/core/MODULE_README.md +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/core/RELATIONS-TASKS.md +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/core/TASKS.md +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/core/__init__.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/core/admin_lane.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/core/async_db_manager.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/core/base.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/core/config.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/core/connections.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/core/db_function.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/core/diagnostics.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/core/expressions.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/core/extended.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/core/fields.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/core/introspection.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/core/listen.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/core/loop_filters.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/core/model_dto.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/core/model_view.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/core/one_database.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/core/paginator.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/core/pool_maintenance.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/core/pool_watch.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/core/pooler_role.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/core/pydantic_bridge.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/core/registry.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/core/relations.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/core/resilience.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/core/rls_session.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/core/session_context.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/core/signals.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/core/tls.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/core/transaction.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/core/types.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/core/write_queue.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/core/write_retry.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/entity.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/error_handling.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/exceptions.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/extended/__init__.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/extended/app_error_handler.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/identity_rule.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/local_sqlite.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/middleware/__init__.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/middleware/base.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/migrations/MODULE_README.md +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/migrations/__init__.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/migrations/cli.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/migrations/ddl.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/migrations/diff.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/migrations/executor.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/migrations/integration.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/migrations/loader.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/migrations/operations.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/migrations/state.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/migrations/table_filter.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/no_org_assignment.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/operations/FEATURE.md +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/operations/MODULE_README.md +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/operations/__init__.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/operations/bulk_update_values.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/operations/catalog_select.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/operations/conflict.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/operations/conflict_writes.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/operations/create.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/operations/db_functions.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/operations/delete.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/operations/dynamic_admin.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/operations/dynamic_crud.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/operations/graph_walk.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/operations/latest_rows.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/operations/queue_claim.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/operations/read.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/operations/relational_backfill.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/operations/staging_load.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/operations/update.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/platform_access.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/platform_db.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/py.typed +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/pytest_live_db_guard.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/pytest_lock_guard.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/pytest_plugin.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/python_sql/MODULE_README.md +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/python_sql/__init__.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/python_sql/db_objects.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/python_sql/table_detailed_relationships.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/python_sql/table_typescript_relationship.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/query/FEATURE.md +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/query/MODULE_README.md +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/query/__init__.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/query/builder.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/query/executor.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/resources/__init__.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/resources/additional-assigners-2026-09-12.json +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/resources/org_assignment_trigger_baseline.json +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/schema_builder/FEATURE.md +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/schema_builder/MODULE_README.md +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/schema_builder/__init__.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/schema_builder/code_handler.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/schema_builder/codegen_writer.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/schema_builder/columns.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/schema_builder/common.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/schema_builder/diff_preview.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/schema_builder/entity_capabilities.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/schema_builder/generator.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/schema_builder/helpers/__init__.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/schema_builder/helpers/base_generators.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/schema_builder/helpers/entity_generators.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/schema_builder/helpers/git_checker.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/schema_builder/package_wiring.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/schema_builder/relationships.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/schema_builder/runner.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/schema_builder/schema.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/schema_builder/schema_manager.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/schema_builder/tables.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/schema_builder/views.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/secrets_battery/FEATURE.md +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/secrets_battery/__init__.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/secrets_battery/attachments.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/secrets_battery/crypto.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/secrets_battery/host_authorization.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/secrets_battery/model.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/secrets_battery/mutation_receipts.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/secrets_battery/native_enable.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/secrets_battery/native_lifecycle.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/secrets_battery/native_passkeys.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/secrets_battery/native_source.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/secrets_battery/org_service.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/secrets_battery/service.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/session/README.md +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/session/__init__.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/session/coalesce.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/session/dag.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/session/errors.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/session/fallback.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/session/flush.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/session/lifecycle.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/session/managed.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/session/op.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/session/reads.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/session/session.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/session/telemetry.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/sql_executor/MODULE_README.md +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/sql_executor/__init__.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/sql_executor/executor.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/sql_executor/queries.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/sql_executor/registry.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/sql_executor/types.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/sql_executor/utils.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/state.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/utils/__init__.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/utils/sql_utils.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/utils/type_converters.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/matrx_orm/write_actor.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/scripts/git-branches.sh +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/scripts/publish.sh +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/scripts/release.sh +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/MODULE_README.md +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/__init__.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/conftest.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/MODULE_README.md +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/__init__.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_adapter_registry.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_admin_db_columns.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_admin_response_rendering.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_admin_search_negation.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_admin_search_sentinels.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_advisory_lock.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_agent_message.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_api_auth.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_api_config.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_api_handlers.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_api_protocol.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_array_agg_order_by.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_association_trigger_actor_naming.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_associations.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_associations_wiring.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_bulk_hydration_offload.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_bulk_update_by_pk.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_bulk_upsert_increment_set_fields.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_cache_debug.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_call_function.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_case_when.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_catalog.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_committed_row_guard.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_composite_pk_filter.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_composite_pk_write_paths.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_config.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_conflict_target.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_conflict_writes.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_connection_codecs.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_connection_poison_guard.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_connection_primitives.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_count_composite_distinct.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_count_limit_param_leak.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_db_function_field.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_ddl_generator.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_deadlock_retry.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_derived_table.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_dirty_tracking.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_dynamic_crud.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_exceptions.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_execute_query_integrity_mapping.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_expression_primitives.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_fields.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_filter_jsonb_agg_array_index.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_fk_cycle_resolution.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_from_alias.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_fts.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_func_expression_args.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_graph_walk.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_having_annotate_alias.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_insert_ignore_and_admin_primitives.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_introspect_rls_policies.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_join_and_aggregate.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_latest_rows.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_listen_notify.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_local_sqlite.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_lock_contention_retry.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_managed_write_guard.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_matrx_entity.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_migration_diff_types.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_migration_loader.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_model_instance.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_model_meta.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_optimistic_concurrency.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_order_by_expression.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_org_assignment_inventory.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_orm_banners_are_client_safe.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_parameter_name_hydration.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_patch_jsonb_path.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_percentile_cont.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_persisted_count.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_pool_create_never_wedges.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_pool_holder_census.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_pool_loop_recreation.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_pool_maintenance.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_postgrest_filters.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_pydantic_bridge.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_query_builder.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_query_error_detail.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_query_executor_sql.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_query_timeout.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_querybuilder_clone_semantics.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_queue_claim.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_read_connection_retry.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_registry.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_registry_multi_database.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_registry_multi_schema.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_relational_backfill.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_relations.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_resilience.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_rls_session_scope.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_schema_exists.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_session_advisory_lock.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_standalone_write_session_context.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_state_cache.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_subquery_filter_raw.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_subquery_in_filter.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_subquery_nested_expressions.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_supabase_auth.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_supabase_config.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_tls.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_transaction_database_identity.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_typed_join_projection.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_typed_predicates.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_update_case_expression.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_update_subquery.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_update_with_rebase.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_upsert_default_leak.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_upsert_with_conflict.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_values_jsonb_decode.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_watched_lifecycle.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_write_queue_deadline.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level1/test_write_retry_semantics.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level2/MODULE_README.md +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level2/__init__.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level2/conftest.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level2/test_bulk_ops.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level2/test_cache_integration.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level2/test_crud.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level2/test_foreign_keys.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level2/test_m2m.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level2/test_manager.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level2/test_migrations_live.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level2/test_query_execution.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level2/test_schema_diff.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/level2/verify_committed_row_guard_pg17.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/live_pooler_role.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/native_source_cases.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/native_source_oracle.rs +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/sample_project/.env.example +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/sample_project/README.md +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/sample_project/__init__.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/sample_project/generate.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/sample_project/generated/.gitkeep +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/sample_project/matrx_orm.yaml +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/sample_project/test_schema_generation.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/sample_project_desktop/.env.example +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/sample_project_desktop/README.md +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/sample_project_desktop/__init__.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/sample_project_desktop/client_example.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/sample_project_desktop/client_example.ts +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/sample_project_desktop/client_supabase_example.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/sample_project_desktop/server.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/schema/entity_tests.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/schema/test_base_generation.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/schema/test_composite_pk_fk_generation.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/schema/test_composite_unique_generation.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/schema/test_dto_identity_generation.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/schema/test_entity_capability_generation.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/schema/test_enum_ordering.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/schema/test_generate_schema.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/schema/test_junction_analysis.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/schema/test_multi_schema_output.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/schema/test_schema_analysis.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/schema/test_sql_expression_defaults.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/schema/test_table_relationships_query.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/schema/test_view_generation.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/schema_builder/test_array_default_parsing.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/schema_builder/test_focused_generation_plan.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/schema_builder/test_host_models_aggregator.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/session/__init__.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/session/conftest.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/session/test_a_failed_capture_still_names_the_failure.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/session/test_cache_no_downgrade.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/session/test_capture_poison_proof.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/session/test_coalesce.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/session/test_dag.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/session/test_disk_spill_fallback.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/session/test_flush_individual_fallback.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/session/test_governed_write.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/session/test_live_db_guard_plugin.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/session/test_managed.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/session/test_op_enqueue_site.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/session/test_reads.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/session/test_record_error_bounded_insert.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/session/test_record_error_storm.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/session/test_session.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/session/test_spill_guard_plugin.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/test_array_arg_element_type.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/test_attachment_mutation_serialization.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/test_backup_alias_conflict.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/test_composite_fk_is_not_a_column_fk.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/test_database_name_alias.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/test_db_functions.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/test_dsn_password_masking.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/test_dynamic_select_unregistered.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/test_immutable_insert_or_exact_verify.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/test_keyed_digest.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/test_model_cls_refactor.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/test_native_enable.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/test_native_lifecycle.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/test_native_lifecycle_fences.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/test_native_passkey_receipts.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/test_native_source.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/test_no_blocking_asyncpg_tls.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/test_one_database.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/test_order_by_desc_nulls_last.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/test_pooler_role_guard.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/test_secret_audit_names_its_organization.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/test_secrets_minting_keys.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/test_shared_lock_guard.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/test_sql_param_casts.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/test_sync_twins_guard.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/test_user_visible_lookup.py +0 -0
- {matrx_orm-3.1.183 → matrx_orm-3.1.185}/tests/test_vault_aggregate_recovery_stateful.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: matrx-orm
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.185
|
|
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.55
|
|
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
|
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
from collections.abc import AsyncIterator
|
|
6
6
|
from contextlib import asynccontextmanager
|
|
7
7
|
from dataclasses import dataclass
|
|
8
|
+
from datetime import UTC, datetime
|
|
8
9
|
from typing import Literal
|
|
9
10
|
from uuid import UUID
|
|
10
11
|
|
|
@@ -34,6 +35,11 @@ from .mutation_receipts import (
|
|
|
34
35
|
reserve_backup_restore_receipt,
|
|
35
36
|
)
|
|
36
37
|
from .org_service import record_secret_audit
|
|
38
|
+
from .vault_history import (
|
|
39
|
+
locked_history_revision,
|
|
40
|
+
password_field_update_snapshot,
|
|
41
|
+
write_password_field_update_snapshot,
|
|
42
|
+
)
|
|
37
43
|
|
|
38
44
|
|
|
39
45
|
@dataclass(frozen=True, repr=False)
|
|
@@ -58,6 +64,15 @@ class BackupRestoreAliasConflictError(Exception):
|
|
|
58
64
|
"""A restored alias is reserved; the entire record was rolled back."""
|
|
59
65
|
|
|
60
66
|
|
|
67
|
+
@dataclass(frozen=True, repr=False)
|
|
68
|
+
class BackupHistoryRevisionSpec:
|
|
69
|
+
"""One portable password state; source ciphertext never enters this boundary."""
|
|
70
|
+
|
|
71
|
+
revision: int
|
|
72
|
+
value: str
|
|
73
|
+
source_recorded_at: datetime | None = None
|
|
74
|
+
|
|
75
|
+
|
|
61
76
|
@asynccontextmanager
|
|
62
77
|
async def _restore_transaction() -> AsyncIterator[None]:
|
|
63
78
|
try:
|
|
@@ -91,6 +106,7 @@ async def create_backup_record_quarantined(
|
|
|
91
106
|
non_secret_fields: list[dict[str, str]],
|
|
92
107
|
fields: list[FieldSpec],
|
|
93
108
|
attachments: list[BackupAttachmentSpec],
|
|
109
|
+
history: list[BackupHistoryRevisionSpec] | None = None,
|
|
94
110
|
) -> BackupRestoreResult:
|
|
95
111
|
"""Restore one host-authorized personal aggregate and receipt in one transaction."""
|
|
96
112
|
# The host supplies verified actor/context and computes the binding fingerprint.
|
|
@@ -114,6 +130,7 @@ async def create_backup_record_quarantined(
|
|
|
114
130
|
# Validation is shared, but restoration must not silently normalize source text.
|
|
115
131
|
metadata["login_urls"] = login_urls
|
|
116
132
|
seen: set[str] = set()
|
|
133
|
+
password_fields: list[FieldSpec] = []
|
|
117
134
|
for field in fields:
|
|
118
135
|
_validate_field_key(field.field_key)
|
|
119
136
|
_refuse_reserved_generic_field(field.field_key)
|
|
@@ -123,6 +140,8 @@ async def create_backup_record_quarantined(
|
|
|
123
140
|
raise ValueError("backup field is not portable")
|
|
124
141
|
if field.inject_into_sandbox:
|
|
125
142
|
raise ValueError("backup cannot enable sandbox injection")
|
|
143
|
+
if field.field_key == "password":
|
|
144
|
+
password_fields.append(field)
|
|
126
145
|
seen.add(field.field_key)
|
|
127
146
|
for attachment in attachments:
|
|
128
147
|
if attachment.source_handling not in {"visible", "revealable"}:
|
|
@@ -133,6 +152,7 @@ async def create_backup_record_quarantined(
|
|
|
133
152
|
_validate_text(attachment.media_type, name="attachment media type", maximum=255)
|
|
134
153
|
if attachment.description is not None and len(attachment.description) > 2000:
|
|
135
154
|
raise ValueError("attachment description is too long")
|
|
155
|
+
history = _validate_history(history, password_fields)
|
|
136
156
|
except (TypeError, ValueError, AttributeError):
|
|
137
157
|
raise ValueError("Invalid backup restore record.") from None
|
|
138
158
|
|
|
@@ -165,8 +185,9 @@ async def create_backup_record_quarantined(
|
|
|
165
185
|
created_by=actor_id,
|
|
166
186
|
updated_by=actor_id,
|
|
167
187
|
)
|
|
188
|
+
password_row: UserSecret | None = None
|
|
168
189
|
for field in fields:
|
|
169
|
-
await UserSecret.create(
|
|
190
|
+
created = await UserSecret.create(
|
|
170
191
|
user_id=actor_id,
|
|
171
192
|
organization_id=None,
|
|
172
193
|
credential_item_id=item.id,
|
|
@@ -184,6 +205,22 @@ async def create_backup_record_quarantined(
|
|
|
184
205
|
created_by=actor_id,
|
|
185
206
|
updated_by=actor_id,
|
|
186
207
|
)
|
|
208
|
+
if field.field_key == "password":
|
|
209
|
+
password_row = created
|
|
210
|
+
if history:
|
|
211
|
+
# The private writer deliberately requires an active field and an
|
|
212
|
+
# active snapshot manifest. This activation is transaction-local:
|
|
213
|
+
# the item remains disabled, no grant is created, and the field is
|
|
214
|
+
# made inactive again before the receipt can commit.
|
|
215
|
+
assert password_row is not None
|
|
216
|
+
await _write_quarantined_history(
|
|
217
|
+
item_id=str(item.id),
|
|
218
|
+
field_id=str(password_row.id),
|
|
219
|
+
actor_id=actor_id,
|
|
220
|
+
audit_organization_id=audit_organization_id,
|
|
221
|
+
handling=password_fields[0].handling,
|
|
222
|
+
history=history,
|
|
223
|
+
)
|
|
187
224
|
for attachment in attachments:
|
|
188
225
|
await CredentialAttachment.create(
|
|
189
226
|
credential_item_id=item.id,
|
|
@@ -215,6 +252,111 @@ async def create_backup_record_quarantined(
|
|
|
215
252
|
return BackupRestoreResult(str(item.id), False)
|
|
216
253
|
|
|
217
254
|
|
|
255
|
+
def _validate_history(
|
|
256
|
+
history: list[BackupHistoryRevisionSpec] | None,
|
|
257
|
+
password_fields: list[FieldSpec],
|
|
258
|
+
) -> tuple[BackupHistoryRevisionSpec, ...]:
|
|
259
|
+
"""Refuse an incomplete portable chain before any restore row is inserted."""
|
|
260
|
+
if history is None:
|
|
261
|
+
return ()
|
|
262
|
+
if not isinstance(history, list):
|
|
263
|
+
raise ValueError("backup history is invalid")
|
|
264
|
+
if not history:
|
|
265
|
+
return ()
|
|
266
|
+
if len(password_fields) != 1 or password_fields[0].handling not in {"visible", "revealable"}:
|
|
267
|
+
raise ValueError("backup history requires one ordinary password field")
|
|
268
|
+
checked: list[BackupHistoryRevisionSpec] = []
|
|
269
|
+
for expected, revision in enumerate(history, start=1):
|
|
270
|
+
if (
|
|
271
|
+
not isinstance(revision, BackupHistoryRevisionSpec)
|
|
272
|
+
or type(revision.revision) is not int
|
|
273
|
+
or revision.revision != expected
|
|
274
|
+
or not isinstance(revision.value, str)
|
|
275
|
+
or (
|
|
276
|
+
revision.source_recorded_at is not None
|
|
277
|
+
and (
|
|
278
|
+
not isinstance(revision.source_recorded_at, datetime)
|
|
279
|
+
or revision.source_recorded_at.tzinfo is None
|
|
280
|
+
or revision.source_recorded_at.utcoffset() is None
|
|
281
|
+
)
|
|
282
|
+
)
|
|
283
|
+
):
|
|
284
|
+
raise ValueError("backup history is invalid")
|
|
285
|
+
checked.append(revision)
|
|
286
|
+
if checked[-1].value != password_fields[0].value:
|
|
287
|
+
raise ValueError("backup history does not match the current password")
|
|
288
|
+
return tuple(checked)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
async def _write_quarantined_history(
|
|
292
|
+
*,
|
|
293
|
+
item_id: str,
|
|
294
|
+
field_id: str,
|
|
295
|
+
actor_id: str,
|
|
296
|
+
audit_organization_id: str,
|
|
297
|
+
handling: str,
|
|
298
|
+
history: tuple[BackupHistoryRevisionSpec, ...],
|
|
299
|
+
) -> None:
|
|
300
|
+
"""Write fresh destination snapshots while preserving final quarantine."""
|
|
301
|
+
if await locked_history_revision(item_id) != 0:
|
|
302
|
+
raise RuntimeError("new backup item has an unexpected history revision")
|
|
303
|
+
first = history[0]
|
|
304
|
+
current = encrypt_value(first.value)
|
|
305
|
+
changed = await UserSecret.update_where(
|
|
306
|
+
{"id": field_id, "deleted_at__isnull": True},
|
|
307
|
+
value_encrypted=current,
|
|
308
|
+
value_hint=mask_value(first.value),
|
|
309
|
+
is_active=True,
|
|
310
|
+
updated_by=actor_id,
|
|
311
|
+
updated_at=datetime.now(UTC),
|
|
312
|
+
)
|
|
313
|
+
if changed.rows_affected != 1:
|
|
314
|
+
raise RuntimeError("backup password field disappeared during restore")
|
|
315
|
+
expected_revision = 0
|
|
316
|
+
started = datetime.now(UTC)
|
|
317
|
+
for revision in history:
|
|
318
|
+
after = current if revision.revision == 1 else encrypt_value(revision.value)
|
|
319
|
+
if revision.revision > 1:
|
|
320
|
+
changed = await UserSecret.update_where(
|
|
321
|
+
{"id": field_id, "deleted_at__isnull": True},
|
|
322
|
+
value_encrypted=after,
|
|
323
|
+
value_hint=mask_value(revision.value),
|
|
324
|
+
updated_by=actor_id,
|
|
325
|
+
updated_at=datetime.now(UTC),
|
|
326
|
+
)
|
|
327
|
+
if changed.rows_affected != 1:
|
|
328
|
+
raise RuntimeError("backup password field disappeared during restore")
|
|
329
|
+
snapshot = password_field_update_snapshot(
|
|
330
|
+
item_id=item_id,
|
|
331
|
+
item_user_id=actor_id,
|
|
332
|
+
audit_organization_id=audit_organization_id,
|
|
333
|
+
field_id=field_id,
|
|
334
|
+
field_key="password",
|
|
335
|
+
handling=handling,
|
|
336
|
+
execution_purpose="general",
|
|
337
|
+
before_encrypted=current,
|
|
338
|
+
after_encrypted=after,
|
|
339
|
+
capture_started_at=started,
|
|
340
|
+
source_recorded_at=revision.source_recorded_at,
|
|
341
|
+
)
|
|
342
|
+
expected_revision = await write_password_field_update_snapshot(
|
|
343
|
+
item_id=item_id,
|
|
344
|
+
actor_id=actor_id,
|
|
345
|
+
audit_organization_id=audit_organization_id,
|
|
346
|
+
expected_revision=expected_revision,
|
|
347
|
+
snapshot=snapshot,
|
|
348
|
+
)
|
|
349
|
+
current = after
|
|
350
|
+
changed = await UserSecret.update_where(
|
|
351
|
+
{"id": field_id, "deleted_at__isnull": True},
|
|
352
|
+
is_active=False,
|
|
353
|
+
updated_by=actor_id,
|
|
354
|
+
updated_at=datetime.now(UTC),
|
|
355
|
+
)
|
|
356
|
+
if changed.rows_affected != 1:
|
|
357
|
+
raise RuntimeError("backup password field disappeared during restore")
|
|
358
|
+
|
|
359
|
+
|
|
218
360
|
async def _validate_replay(item_id: str, actor_id: str) -> None:
|
|
219
361
|
rows = (
|
|
220
362
|
await CredentialItem.filter(id=item_id, deleted_at__isnull=True)
|
|
@@ -1290,6 +1290,109 @@ async def add_credential_field(
|
|
|
1290
1290
|
return _field_row(rows[0])
|
|
1291
1291
|
|
|
1292
1292
|
|
|
1293
|
+
def _captures_personal_password_history(
|
|
1294
|
+
item_raw: dict[str, Any] | None,
|
|
1295
|
+
field_raw: dict[str, Any],
|
|
1296
|
+
) -> bool:
|
|
1297
|
+
"""Return whether this locked row belongs in the private password history.
|
|
1298
|
+
|
|
1299
|
+
The module-local gate remains deliberately false in production. Keeping
|
|
1300
|
+
eligibility here gives every existing-field ciphertext writer the same
|
|
1301
|
+
future activation boundary without adding a caller-controlled switch.
|
|
1302
|
+
"""
|
|
1303
|
+
return (
|
|
1304
|
+
_PASSWORD_HISTORY_CAPTURE_ENABLED
|
|
1305
|
+
and item_raw is not None
|
|
1306
|
+
and item_raw.get("user_id") is not None
|
|
1307
|
+
and item_raw.get("organization_id") is None
|
|
1308
|
+
and field_raw.get("field_key") == "password"
|
|
1309
|
+
and field_raw.get("handling") in {"visible", "revealable"}
|
|
1310
|
+
and field_raw.get("execution_purpose") == GENERAL_EXECUTION_PURPOSE
|
|
1311
|
+
and field_raw.get("is_active") is True
|
|
1312
|
+
)
|
|
1313
|
+
|
|
1314
|
+
|
|
1315
|
+
async def _prepare_personal_password_history_capture(
|
|
1316
|
+
*,
|
|
1317
|
+
item_raw: dict[str, Any] | None,
|
|
1318
|
+
field_raw: dict[str, Any],
|
|
1319
|
+
actor_id: str | None,
|
|
1320
|
+
audit_organization_id: str | None,
|
|
1321
|
+
) -> tuple[int, datetime] | None:
|
|
1322
|
+
"""Write the single allowed bootstrap before an eligible ciphertext change.
|
|
1323
|
+
|
|
1324
|
+
The caller already holds both the item and field locks. Returning the
|
|
1325
|
+
locked next revision makes the later delta conditional on the same chain.
|
|
1326
|
+
"""
|
|
1327
|
+
if not _captures_personal_password_history(item_raw, field_raw):
|
|
1328
|
+
return None
|
|
1329
|
+
if not actor_id or not audit_organization_id:
|
|
1330
|
+
raise VaultHistorySnapshotError(
|
|
1331
|
+
"personal password history requires actor and admitted audit organization"
|
|
1332
|
+
)
|
|
1333
|
+
assert item_raw is not None
|
|
1334
|
+
item_id = str(item_raw["id"])
|
|
1335
|
+
capture_started_at = datetime.now(UTC)
|
|
1336
|
+
revision = await locked_history_revision(item_id)
|
|
1337
|
+
if revision == 0:
|
|
1338
|
+
encrypted = field_raw["value_encrypted"]
|
|
1339
|
+
bootstrap = password_field_update_snapshot(
|
|
1340
|
+
item_id=item_id,
|
|
1341
|
+
item_user_id=str(item_raw["user_id"]),
|
|
1342
|
+
audit_organization_id=audit_organization_id,
|
|
1343
|
+
field_id=str(field_raw["id"]),
|
|
1344
|
+
field_key=str(field_raw["field_key"]),
|
|
1345
|
+
handling=str(field_raw["handling"]),
|
|
1346
|
+
execution_purpose=str(field_raw["execution_purpose"]),
|
|
1347
|
+
before_encrypted=encrypted,
|
|
1348
|
+
after_encrypted=encrypted,
|
|
1349
|
+
capture_started_at=capture_started_at,
|
|
1350
|
+
)
|
|
1351
|
+
revision = await write_password_field_update_snapshot(
|
|
1352
|
+
item_id=item_id,
|
|
1353
|
+
actor_id=actor_id,
|
|
1354
|
+
audit_organization_id=audit_organization_id,
|
|
1355
|
+
expected_revision=revision,
|
|
1356
|
+
snapshot=bootstrap,
|
|
1357
|
+
)
|
|
1358
|
+
return revision, capture_started_at
|
|
1359
|
+
|
|
1360
|
+
|
|
1361
|
+
async def _append_personal_password_history_capture(
|
|
1362
|
+
*,
|
|
1363
|
+
capture: tuple[int, datetime] | None,
|
|
1364
|
+
item_raw: dict[str, Any] | None,
|
|
1365
|
+
field_raw: dict[str, Any],
|
|
1366
|
+
after_encrypted: Any,
|
|
1367
|
+
actor_id: str | None,
|
|
1368
|
+
audit_organization_id: str | None,
|
|
1369
|
+
) -> None:
|
|
1370
|
+
"""Append the locked before-to-after delta after its ciphertext update."""
|
|
1371
|
+
if capture is None:
|
|
1372
|
+
return
|
|
1373
|
+
assert item_raw is not None and actor_id and audit_organization_id
|
|
1374
|
+
revision, capture_started_at = capture
|
|
1375
|
+
snapshot = password_field_update_snapshot(
|
|
1376
|
+
item_id=str(item_raw["id"]),
|
|
1377
|
+
item_user_id=str(item_raw["user_id"]),
|
|
1378
|
+
audit_organization_id=audit_organization_id,
|
|
1379
|
+
field_id=str(field_raw["id"]),
|
|
1380
|
+
field_key=str(field_raw["field_key"]),
|
|
1381
|
+
handling=str(field_raw["handling"]),
|
|
1382
|
+
execution_purpose=str(field_raw["execution_purpose"]),
|
|
1383
|
+
before_encrypted=field_raw["value_encrypted"],
|
|
1384
|
+
after_encrypted=after_encrypted,
|
|
1385
|
+
capture_started_at=capture_started_at,
|
|
1386
|
+
)
|
|
1387
|
+
await write_password_field_update_snapshot(
|
|
1388
|
+
item_id=str(item_raw["id"]),
|
|
1389
|
+
actor_id=actor_id,
|
|
1390
|
+
audit_organization_id=audit_organization_id,
|
|
1391
|
+
expected_revision=revision,
|
|
1392
|
+
snapshot=snapshot,
|
|
1393
|
+
)
|
|
1394
|
+
|
|
1395
|
+
|
|
1293
1396
|
async def update_credential_field_value(
|
|
1294
1397
|
field_id: str,
|
|
1295
1398
|
value: str,
|
|
@@ -1323,30 +1426,12 @@ async def update_credential_field_value(
|
|
|
1323
1426
|
f"field {raw.get('field_key') or raw.get('key')!r} is not editable — "
|
|
1324
1427
|
"it is managed by its owning integration"
|
|
1325
1428
|
)
|
|
1326
|
-
capture = (
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1429
|
+
capture = await _prepare_personal_password_history_capture(
|
|
1430
|
+
item_raw=item_raw,
|
|
1431
|
+
field_raw=raw,
|
|
1432
|
+
actor_id=actor_id,
|
|
1433
|
+
audit_organization_id=audit_organization_id,
|
|
1331
1434
|
)
|
|
1332
|
-
history_revision: int | None = None
|
|
1333
|
-
capture_started_at = datetime.now(UTC)
|
|
1334
|
-
if capture:
|
|
1335
|
-
if not actor_id or not audit_organization_id:
|
|
1336
|
-
raise VaultHistorySnapshotError("personal password history requires actor and admitted audit organization")
|
|
1337
|
-
history_revision = await locked_history_revision(str(item_raw["id"]))
|
|
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
|
-
)
|
|
1350
1435
|
encrypted_value = encrypt_value(value)
|
|
1351
1436
|
await UserSecret.update_where(
|
|
1352
1437
|
{"id": field_id, "deleted_at__isnull": True},
|
|
@@ -1355,19 +1440,14 @@ async def update_credential_field_value(
|
|
|
1355
1440
|
updated_by=actor_id,
|
|
1356
1441
|
updated_at=datetime.now(UTC),
|
|
1357
1442
|
)
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
)
|
|
1367
|
-
await write_password_field_update_snapshot(
|
|
1368
|
-
item_id=str(item_raw["id"]), actor_id=actor_id, audit_organization_id=audit_organization_id,
|
|
1369
|
-
expected_revision=history_revision, snapshot=after,
|
|
1370
|
-
)
|
|
1443
|
+
await _append_personal_password_history_capture(
|
|
1444
|
+
capture=capture,
|
|
1445
|
+
item_raw=item_raw,
|
|
1446
|
+
field_raw=raw,
|
|
1447
|
+
after_encrypted=encrypted_value,
|
|
1448
|
+
actor_id=actor_id,
|
|
1449
|
+
audit_organization_id=audit_organization_id,
|
|
1450
|
+
)
|
|
1371
1451
|
await record_secret_audit(
|
|
1372
1452
|
action="rotated" if system_rotation else "field_updated",
|
|
1373
1453
|
actor_id=actor_id,
|
|
@@ -1408,22 +1488,43 @@ async def rotate_credential_fields(
|
|
|
1408
1488
|
item_raw = await _lock_item_raw(item_id)
|
|
1409
1489
|
rows = await UserSecret.filter(
|
|
1410
1490
|
credential_item_id=item_id, field_key__in=list(new_values), deleted_at__isnull=True
|
|
1411
|
-
).values(
|
|
1491
|
+
).select_for_update().values(
|
|
1492
|
+
"id", "field_key", "credential_item_id", "execution_purpose", "handling", "is_active", "value_encrypted"
|
|
1493
|
+
)
|
|
1412
1494
|
protected = [row for row in rows if _is_protected(row)]
|
|
1413
1495
|
if protected:
|
|
1414
1496
|
_refuse_protected(protected[0])
|
|
1415
|
-
found = {str(r["field_key"]):
|
|
1497
|
+
found = {str(r["field_key"]): r for r in rows}
|
|
1416
1498
|
missing = sorted(set(new_values) - set(found))
|
|
1417
1499
|
if missing:
|
|
1418
1500
|
raise SecretNotFoundError(f"item {item_id} has no fields {missing} to rotate")
|
|
1501
|
+
captures = {
|
|
1502
|
+
fk: await _prepare_personal_password_history_capture(
|
|
1503
|
+
item_raw=item_raw,
|
|
1504
|
+
field_raw=field_raw,
|
|
1505
|
+
actor_id=actor_id,
|
|
1506
|
+
audit_organization_id=audit_organization_id,
|
|
1507
|
+
)
|
|
1508
|
+
for fk, field_raw in found.items()
|
|
1509
|
+
}
|
|
1419
1510
|
for fk, plaintext in new_values.items():
|
|
1511
|
+
field_raw = found[fk]
|
|
1512
|
+
encrypted_value = encrypt_value(plaintext)
|
|
1420
1513
|
await UserSecret.update_where(
|
|
1421
|
-
{"id":
|
|
1422
|
-
value_encrypted=
|
|
1514
|
+
{"id": str(field_raw["id"])},
|
|
1515
|
+
value_encrypted=encrypted_value,
|
|
1423
1516
|
value_hint=mask_value(plaintext),
|
|
1424
1517
|
updated_by=actor_id,
|
|
1425
1518
|
updated_at=now,
|
|
1426
1519
|
)
|
|
1520
|
+
await _append_personal_password_history_capture(
|
|
1521
|
+
capture=captures[fk],
|
|
1522
|
+
item_raw=item_raw,
|
|
1523
|
+
field_raw=field_raw,
|
|
1524
|
+
after_encrypted=encrypted_value,
|
|
1525
|
+
actor_id=actor_id,
|
|
1526
|
+
audit_organization_id=audit_organization_id,
|
|
1527
|
+
)
|
|
1427
1528
|
await record_secret_audit(
|
|
1428
1529
|
action="rotated",
|
|
1429
1530
|
actor_id=actor_id,
|
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import re
|
|
6
6
|
import uuid
|
|
7
7
|
from datetime import UTC, datetime
|
|
8
|
+
from math import isfinite
|
|
8
9
|
from typing import Any
|
|
9
10
|
|
|
10
11
|
from matrx_orm import call_function
|
|
@@ -22,6 +23,7 @@ _ENVELOPE_KEYS = {
|
|
|
22
23
|
"schema_version", "snapshot_id", "mutation_kind", "original_ownership",
|
|
23
24
|
"audit_organization_id", "capture_started_at", "component_manifest", "changed_components",
|
|
24
25
|
}
|
|
26
|
+
_ENVELOPE_KEYS_WITH_SOURCE_RECORDED_AT = _ENVELOPE_KEYS | {"source_recorded_at"}
|
|
25
27
|
|
|
26
28
|
|
|
27
29
|
class VaultHistorySnapshotError(ValueError):
|
|
@@ -44,10 +46,21 @@ def _state(value_encrypted: Any, key_fingerprint: str) -> dict[str, str]:
|
|
|
44
46
|
return {"value_encrypted": _ciphertext(value_encrypted), "key_fingerprint": key_fingerprint}
|
|
45
47
|
|
|
46
48
|
|
|
49
|
+
def _source_recorded_at(value: datetime) -> str:
|
|
50
|
+
if not isinstance(value, datetime) or value.tzinfo is None or value.utcoffset() is None:
|
|
51
|
+
raise VaultHistorySnapshotError("Vault history source timestamp must be timezone-aware")
|
|
52
|
+
try:
|
|
53
|
+
if not isfinite(value.timestamp()):
|
|
54
|
+
raise ValueError
|
|
55
|
+
except (OverflowError, OSError, ValueError) as exc:
|
|
56
|
+
raise VaultHistorySnapshotError("Vault history source timestamp must be finite") from exc
|
|
57
|
+
return value.astimezone(UTC).isoformat()
|
|
58
|
+
|
|
59
|
+
|
|
47
60
|
def password_field_update_snapshot(
|
|
48
61
|
*, item_id: str, item_user_id: str, audit_organization_id: str, field_id: str,
|
|
49
62
|
field_key: str, handling: str, execution_purpose: str, before_encrypted: Any,
|
|
50
|
-
after_encrypted: Any, capture_started_at: datetime,
|
|
63
|
+
after_encrypted: Any, capture_started_at: datetime, source_recorded_at: datetime | None = None,
|
|
51
64
|
) -> dict[str, Any]:
|
|
52
65
|
"""Build the only Gate-B shape accepted for an ordinary password update."""
|
|
53
66
|
if field_key != "password" or handling not in {"visible", "revealable"} or execution_purpose != "general":
|
|
@@ -79,13 +92,18 @@ def password_field_update_snapshot(
|
|
|
79
92
|
"component_manifest": [manifest],
|
|
80
93
|
"changed_components": [change],
|
|
81
94
|
}
|
|
95
|
+
if source_recorded_at is not None:
|
|
96
|
+
snapshot["source_recorded_at"] = _source_recorded_at(source_recorded_at)
|
|
82
97
|
validate_snapshot_v1(snapshot)
|
|
83
98
|
return snapshot
|
|
84
99
|
|
|
85
100
|
|
|
86
101
|
def validate_snapshot_v1(snapshot: dict[str, Any]) -> None:
|
|
87
102
|
"""Reject accidental plaintext, hints, unknown keys, and incomplete v1 states."""
|
|
88
|
-
if
|
|
103
|
+
if (
|
|
104
|
+
set(snapshot) != _ENVELOPE_KEYS
|
|
105
|
+
and set(snapshot) != _ENVELOPE_KEYS_WITH_SOURCE_RECORDED_AT
|
|
106
|
+
) or snapshot.get("schema_version") != 1 or snapshot.get("mutation_kind") != "update":
|
|
89
107
|
raise VaultHistorySnapshotError("Vault history snapshot is not the closed v1 envelope")
|
|
90
108
|
try:
|
|
91
109
|
uuid.UUID(str(snapshot["snapshot_id"]))
|
|
@@ -96,6 +114,11 @@ def validate_snapshot_v1(snapshot: dict[str, Any]) -> None:
|
|
|
96
114
|
raise VaultHistorySnapshotError("Vault history snapshot has invalid identity data") from exc
|
|
97
115
|
if set(snapshot["original_ownership"]) != {"user_id"}:
|
|
98
116
|
raise VaultHistorySnapshotError("Vault history snapshot must have one personal owner")
|
|
117
|
+
if "source_recorded_at" in snapshot:
|
|
118
|
+
try:
|
|
119
|
+
_source_recorded_at(datetime.fromisoformat(str(snapshot["source_recorded_at"]).replace("Z", "+00:00")))
|
|
120
|
+
except (TypeError, ValueError) as exc:
|
|
121
|
+
raise VaultHistorySnapshotError("Vault history source timestamp is invalid") from exc
|
|
99
122
|
manifest = snapshot["component_manifest"]
|
|
100
123
|
changes = snapshot["changed_components"]
|
|
101
124
|
if not isinstance(manifest, list) or len(manifest) != 1 or not isinstance(changes, list) or len(changes) != 1:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "matrx-orm"
|
|
3
|
-
version = "3.1.
|
|
3
|
+
version = "3.1.185"
|
|
4
4
|
description = "Async-first PostgreSQL ORM with bidirectional migrations, schema introspection, many-to-many relationships, and built-in state caching"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = { text = "MIT" }
|
|
@@ -41,7 +41,7 @@ dependencies = [
|
|
|
41
41
|
"gitpython>=3.1.40",
|
|
42
42
|
"psycopg[binary]>=3.2.5",
|
|
43
43
|
"psycopg-pool>=3.2.5",
|
|
44
|
-
"matrx-utils>=2.0.
|
|
44
|
+
"matrx-utils>=2.0.55",
|
|
45
45
|
"pyyaml>=6.0.3",
|
|
46
46
|
"python-dotenv>=1.0",
|
|
47
47
|
"cryptography>=43.0",
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""Portable backup history is all-or-nothing before database insertion."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from types import SimpleNamespace
|
|
6
|
+
from datetime import UTC, datetime
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from matrx_orm.secrets_battery.backup_restore import (
|
|
11
|
+
BackupHistoryRevisionSpec,
|
|
12
|
+
_validate_history,
|
|
13
|
+
)
|
|
14
|
+
from matrx_orm.secrets_battery.items import FieldSpec
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _password(value: str = "current") -> list[FieldSpec]:
|
|
18
|
+
return [FieldSpec(field_key="password", value=value, handling="revealable")]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _revision(number: int, value: str) -> BackupHistoryRevisionSpec:
|
|
22
|
+
return BackupHistoryRevisionSpec(number, value)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_history_requires_complete_chain_ending_at_current_password():
|
|
26
|
+
result = _validate_history(
|
|
27
|
+
[_revision(1, "old"), _revision(2, "current")],
|
|
28
|
+
_password(),
|
|
29
|
+
)
|
|
30
|
+
assert [entry.revision for entry in result] == [1, 2]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_history_allows_a_same_plaintext_delta_when_the_source_ciphertext_rotated():
|
|
34
|
+
result = _validate_history(
|
|
35
|
+
[_revision(1, "current"), _revision(2, "current")], _password()
|
|
36
|
+
)
|
|
37
|
+
assert len(result) == 2
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_history_carries_an_aware_source_timestamp_without_using_it_for_ordering():
|
|
41
|
+
result = _validate_history(
|
|
42
|
+
[BackupHistoryRevisionSpec(1, "current", datetime(2040, 1, 1, tzinfo=UTC))],
|
|
43
|
+
_password(),
|
|
44
|
+
)
|
|
45
|
+
assert result[0].source_recorded_at == datetime(2040, 1, 1, tzinfo=UTC)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@pytest.mark.parametrize(
|
|
49
|
+
("history", "fields"),
|
|
50
|
+
[
|
|
51
|
+
([_revision(2, "current")], _password()),
|
|
52
|
+
([_revision(1, "old")], _password("current")),
|
|
53
|
+
([_revision(1, "current")], []),
|
|
54
|
+
([_revision(1, "current")], [FieldSpec(field_key="password", value="current", handling="sealed")]),
|
|
55
|
+
],
|
|
56
|
+
)
|
|
57
|
+
def test_history_refuses_gap_sealed_or_current_value_mismatch_before_insert(history, fields):
|
|
58
|
+
with pytest.raises(ValueError):
|
|
59
|
+
_validate_history(history, fields)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@pytest.mark.asyncio
|
|
63
|
+
@pytest.mark.parametrize(
|
|
64
|
+
("fields", "history"),
|
|
65
|
+
[
|
|
66
|
+
(_password(), [_revision(2, "current")]),
|
|
67
|
+
(
|
|
68
|
+
_password() + [FieldSpec(field_key="passkey_private", value="protected")],
|
|
69
|
+
[_revision(1, "current")],
|
|
70
|
+
),
|
|
71
|
+
],
|
|
72
|
+
)
|
|
73
|
+
async def test_invalid_history_or_protected_field_refuses_before_opening_restore_transaction(
|
|
74
|
+
monkeypatch, fields, history
|
|
75
|
+
):
|
|
76
|
+
from matrx_orm.secrets_battery import backup_restore
|
|
77
|
+
|
|
78
|
+
opened = False
|
|
79
|
+
|
|
80
|
+
def forbidden_transaction(*_args, **_kwargs):
|
|
81
|
+
nonlocal opened
|
|
82
|
+
opened = True
|
|
83
|
+
raise AssertionError("history validation must precede database insertion")
|
|
84
|
+
|
|
85
|
+
monkeypatch.setattr(backup_restore, "transaction", forbidden_transaction)
|
|
86
|
+
with pytest.raises(ValueError, match="Invalid backup restore record"):
|
|
87
|
+
await backup_restore.create_backup_record_quarantined(
|
|
88
|
+
actor_id="11111111-1111-4111-8111-111111111111",
|
|
89
|
+
audit_organization_id="22222222-2222-4222-8222-222222222222",
|
|
90
|
+
mutation_id="33333333-3333-4333-8333-333333333333",
|
|
91
|
+
request_fingerprint=b"x" * 32,
|
|
92
|
+
display_name="test",
|
|
93
|
+
definition_key="website_login",
|
|
94
|
+
definition_version=1,
|
|
95
|
+
provider_key=None,
|
|
96
|
+
description=None,
|
|
97
|
+
tags=[],
|
|
98
|
+
login_urls=[],
|
|
99
|
+
uri_match_mode=None,
|
|
100
|
+
notes=None,
|
|
101
|
+
non_secret_fields=[],
|
|
102
|
+
fields=fields,
|
|
103
|
+
attachments=[],
|
|
104
|
+
history=history,
|
|
105
|
+
)
|
|
106
|
+
assert opened is False
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@pytest.mark.asyncio
|
|
110
|
+
async def test_same_plaintext_delta_is_written_with_fresh_destination_ciphertext(monkeypatch):
|
|
111
|
+
from matrx_orm.secrets_battery import backup_restore, crypto
|
|
112
|
+
|
|
113
|
+
snapshots = []
|
|
114
|
+
|
|
115
|
+
async def revision(_item_id):
|
|
116
|
+
return 0
|
|
117
|
+
|
|
118
|
+
async def update(*_args, **_kwargs):
|
|
119
|
+
return SimpleNamespace(rows_affected=1)
|
|
120
|
+
|
|
121
|
+
async def write(**kwargs):
|
|
122
|
+
snapshots.append(kwargs["snapshot"])
|
|
123
|
+
return kwargs["expected_revision"] + 1
|
|
124
|
+
|
|
125
|
+
monkeypatch.setenv("CREDENTIALS_ENCRYPTION_KEY", "backup-history-test-key")
|
|
126
|
+
crypto.reset_encryption_for_tests()
|
|
127
|
+
monkeypatch.setattr(backup_restore, "locked_history_revision", revision)
|
|
128
|
+
monkeypatch.setattr(backup_restore.UserSecret, "update_where", update)
|
|
129
|
+
monkeypatch.setattr(backup_restore, "write_password_field_update_snapshot", write)
|
|
130
|
+
await backup_restore._write_quarantined_history(
|
|
131
|
+
item_id="11111111-1111-4111-8111-111111111111",
|
|
132
|
+
field_id="22222222-2222-4222-8222-222222222222",
|
|
133
|
+
actor_id="33333333-3333-4333-8333-333333333333",
|
|
134
|
+
audit_organization_id="44444444-4444-4444-8444-444444444444",
|
|
135
|
+
handling="revealable",
|
|
136
|
+
history=(
|
|
137
|
+
BackupHistoryRevisionSpec(1, "same", datetime(2020, 1, 1, tzinfo=UTC)),
|
|
138
|
+
BackupHistoryRevisionSpec(2, "same", datetime(2020, 1, 2, tzinfo=UTC)),
|
|
139
|
+
),
|
|
140
|
+
)
|
|
141
|
+
assert snapshots[1]["changed_components"][0]["before"] != snapshots[1]["changed_components"][0]["after"]
|
|
142
|
+
assert [snapshot["source_recorded_at"] for snapshot in snapshots] == [
|
|
143
|
+
"2020-01-01T00:00:00+00:00", "2020-01-02T00:00:00+00:00"
|
|
144
|
+
]
|