matrx-orm 3.1.140__tar.gz → 3.1.142__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.140 → matrx_orm-3.1.142}/CLAUDE.md +1 -1
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/PKG-INFO +2 -2
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/core/async_db_manager.py +31 -9
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/secrets_battery/FEATURE.md +10 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/secrets_battery/__init__.py +2 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/secrets_battery/items.py +181 -74
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/secrets_battery/model.py +1 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/secrets_battery/org_service.py +92 -43
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/secrets_battery/service.py +9 -3
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/pyproject.toml +2 -2
- matrx_orm-3.1.142/tests/level1/test_standalone_write_session_context.py +139 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/test_secrets_battery.py +79 -2
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/test_secrets_minting_keys.py +5 -0
- matrx_orm-3.1.140/tests/level1/test_standalone_write_session_context.py +0 -81
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/.gitignore +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/AGENTS.md +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/BASE_CLASS_METHODS.md +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/MODEL_API.md +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/README.md +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/database/orm/extended/managers/ai_model_base.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/docs/migrations.md +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/FEATURE.md +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/MODULE_README.md +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/__init__.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/adapters/MODULE_README.md +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/adapters/__init__.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/adapters/async_postgresql.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/adapters/base_adapter.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/adapters/postgresql.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/adapters/postgrest_client_adapter.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/adapters/supabase_adapter.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/admin/FEATURE.md +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/admin/__init__.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/admin/router.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/api/__init__.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/api/auth.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/api/config.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/api/handlers.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/api/protocol.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/api/server.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/associations.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/cache_debug.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/catalog.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/catalog_sql.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/client/MODULE_README.md +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/client/__init__.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/client/postgres_connection.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/client/postgrest.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/client/supabase_auth.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/client/supabase_config.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/client/supabase_manager.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/core/EXTENDED-TASK.md +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/core/FEATURE.md +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/core/MODULE_README.md +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/core/RELATIONS-TASKS.md +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/core/TASKS.md +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/core/__init__.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/core/base.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/core/config.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/core/connections.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/core/db_function.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/core/diagnostics.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/core/expressions.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/core/extended.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/core/fields.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/core/introspection.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/core/listen.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/core/loop_filters.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/core/model_dto.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/core/model_view.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/core/one_database.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/core/paginator.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/core/pool_maintenance.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/core/pool_watch.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/core/pooler_role.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/core/pydantic_bridge.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/core/registry.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/core/relations.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/core/resilience.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/core/rls_session.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/core/session_context.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/core/signals.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/core/tls.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/core/transaction.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/core/types.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/core/write_queue.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/entity.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/error_handling.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/exceptions.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/extended/__init__.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/extended/app_error_handler.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/identity_rule.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/local_sqlite.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/middleware/__init__.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/middleware/base.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/migrations/MODULE_README.md +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/migrations/__init__.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/migrations/cli.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/migrations/ddl.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/migrations/diff.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/migrations/executor.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/migrations/integration.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/migrations/loader.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/migrations/operations.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/migrations/state.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/migrations/table_filter.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/no_org_assignment.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/operations/FEATURE.md +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/operations/MODULE_README.md +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/operations/__init__.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/operations/bulk_update_values.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/operations/catalog_select.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/operations/conflict.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/operations/conflict_writes.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/operations/create.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/operations/db_functions.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/operations/delete.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/operations/dynamic_admin.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/operations/dynamic_crud.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/operations/graph_walk.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/operations/latest_rows.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/operations/queue_claim.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/operations/read.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/operations/relational_backfill.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/operations/staging_load.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/operations/update.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/platform_access.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/platform_db.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/py.typed +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/pytest_plugin.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/python_sql/MODULE_README.md +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/python_sql/__init__.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/python_sql/db_objects.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/python_sql/table_detailed_relationships.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/python_sql/table_typescript_relationship.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/query/FEATURE.md +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/query/MODULE_README.md +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/query/__init__.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/query/builder.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/query/executor.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/resources/__init__.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/resources/additional-assigners-2026-09-12.json +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/resources/org_assignment_trigger_baseline.json +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/schema_builder/FEATURE.md +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/schema_builder/MODULE_README.md +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/schema_builder/__init__.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/schema_builder/code_handler.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/schema_builder/codegen_writer.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/schema_builder/columns.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/schema_builder/common.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/schema_builder/diff_preview.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/schema_builder/entity_capabilities.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/schema_builder/generator.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/schema_builder/helpers/__init__.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/schema_builder/helpers/base_generators.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/schema_builder/helpers/entity_generators.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/schema_builder/helpers/git_checker.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/schema_builder/package_wiring.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/schema_builder/relationships.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/schema_builder/runner.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/schema_builder/schema.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/schema_builder/schema_manager.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/schema_builder/tables.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/schema_builder/views.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/secrets_battery/attachments.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/secrets_battery/crypto.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/secrets_battery/host_authorization.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/secrets_battery/mutation_receipts.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/session/README.md +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/session/__init__.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/session/coalesce.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/session/dag.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/session/errors.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/session/fallback.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/session/flush.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/session/lifecycle.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/session/managed.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/session/op.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/session/reads.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/session/session.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/session/telemetry.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/sql_executor/MODULE_README.md +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/sql_executor/__init__.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/sql_executor/executor.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/sql_executor/queries.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/sql_executor/registry.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/sql_executor/types.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/sql_executor/utils.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/state.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/utils/__init__.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/utils/sql_utils.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/matrx_orm/utils/type_converters.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/scripts/git-branches.sh +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/scripts/publish.sh +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/scripts/release.sh +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/MODULE_README.md +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/__init__.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/conftest.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/MODULE_README.md +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/__init__.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_adapter_registry.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_admin_db_columns.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_admin_response_rendering.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_admin_search_negation.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_admin_search_sentinels.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_advisory_lock.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_agent_message.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_api_auth.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_api_config.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_api_handlers.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_api_protocol.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_array_agg_order_by.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_associations.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_associations_wiring.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_bulk_hydration_offload.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_bulk_update_by_pk.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_bulk_upsert_increment_set_fields.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_cache_debug.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_call_function.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_case_when.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_catalog.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_composite_pk_filter.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_composite_pk_write_paths.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_config.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_conflict_target.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_conflict_writes.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_connection_codecs.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_connection_poison_guard.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_connection_primitives.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_count_composite_distinct.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_count_limit_param_leak.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_db_function_field.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_ddl_generator.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_derived_table.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_dirty_tracking.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_dynamic_crud.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_exceptions.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_execute_query_integrity_mapping.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_expression_primitives.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_fields.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_filter_jsonb_agg_array_index.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_from_alias.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_fts.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_func_expression_args.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_graph_walk.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_having_annotate_alias.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_insert_ignore_and_admin_primitives.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_introspect_rls_policies.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_join_and_aggregate.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_latest_rows.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_listen_notify.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_local_sqlite.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_lock_contention_retry.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_managed_write_guard.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_matrx_entity.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_migration_diff_types.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_migration_loader.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_model_instance.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_model_meta.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_optimistic_concurrency.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_order_by_expression.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_org_assignment_inventory.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_parameter_name_hydration.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_patch_jsonb_path.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_persisted_count.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_pool_holder_census.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_pool_loop_recreation.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_pool_maintenance.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_postgrest_filters.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_pydantic_bridge.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_query_builder.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_query_error_detail.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_query_executor_sql.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_query_timeout.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_querybuilder_clone_semantics.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_queue_claim.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_read_connection_retry.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_registry.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_registry_multi_database.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_registry_multi_schema.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_relational_backfill.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_relations.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_resilience.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_rls_session_scope.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_schema_exists.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_session_advisory_lock.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_state_cache.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_subquery_filter_raw.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_subquery_in_filter.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_subquery_nested_expressions.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_supabase_auth.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_supabase_config.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_tls.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_typed_join_projection.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_typed_predicates.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_update_case_expression.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_update_subquery.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_update_with_rebase.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_upsert_default_leak.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_upsert_with_conflict.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_values_jsonb_decode.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_watched_lifecycle.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_write_queue_deadline.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level1/test_write_retry_semantics.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level2/MODULE_README.md +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level2/__init__.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level2/conftest.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level2/test_bulk_ops.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level2/test_cache_integration.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level2/test_crud.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level2/test_foreign_keys.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level2/test_m2m.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level2/test_manager.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level2/test_migrations_live.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level2/test_query_execution.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/level2/test_schema_diff.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/live_pooler_role.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/sample_project/.env.example +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/sample_project/README.md +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/sample_project/__init__.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/sample_project/generate.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/sample_project/generated/.gitkeep +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/sample_project/matrx_orm.yaml +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/sample_project/test_schema_generation.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/sample_project_desktop/.env.example +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/sample_project_desktop/README.md +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/sample_project_desktop/__init__.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/sample_project_desktop/client_example.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/sample_project_desktop/client_example.ts +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/sample_project_desktop/client_supabase_example.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/sample_project_desktop/server.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/schema/entity_tests.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/schema/test_base_generation.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/schema/test_composite_pk_fk_generation.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/schema/test_composite_unique_generation.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/schema/test_dto_identity_generation.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/schema/test_entity_capability_generation.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/schema/test_enum_ordering.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/schema/test_generate_schema.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/schema/test_junction_analysis.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/schema/test_multi_schema_output.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/schema/test_schema_analysis.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/schema/test_sql_expression_defaults.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/schema/test_table_relationships_query.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/schema/test_view_generation.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/schema_builder/test_array_default_parsing.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/schema_builder/test_focused_generation_plan.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/schema_builder/test_host_models_aggregator.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/session/__init__.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/session/conftest.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/session/test_cache_no_downgrade.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/session/test_capture_poison_proof.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/session/test_coalesce.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/session/test_dag.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/session/test_disk_spill_fallback.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/session/test_flush_individual_fallback.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/session/test_governed_write.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/session/test_managed.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/session/test_op_enqueue_site.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/session/test_reads.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/session/test_record_error_bounded_insert.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/session/test_record_error_storm.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/session/test_session.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/session/test_spill_guard_plugin.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/test_array_arg_element_type.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/test_database_name_alias.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/test_db_functions.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/test_dynamic_select_unregistered.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/test_keyed_digest.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/test_model_cls_refactor.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/test_no_blocking_asyncpg_tls.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/test_one_database.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/test_order_by_desc_nulls_last.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/test_pooler_role_guard.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/test_sql_param_casts.py +0 -0
- {matrx_orm-3.1.140 → matrx_orm-3.1.142}/tests/test_sync_twins_guard.py +0 -0
|
@@ -52,7 +52,7 @@ Every table that becomes a Model needs row identity: a single-column primary key
|
|
|
52
52
|
- **Schema builder** (`matrx_orm.schema_builder`): code generation for Python + TypeScript type definitions from the DB schema. Postgres **VIEWs** are emitted as **read-only `Model` classes** into the same schema models file (on by default; per-entry `output.include_views: false` opts a schema out, `output.exclude_views: [...]` skips individual views). View models get no manager/DTO/base-class/auto_config (views are not writable), synthesize `_primary_keys` from an `id` column (else the first column, for cache-key/`load_by_id` only), and emit plain typed fields with **no `ForeignKey`** (views carry no FK metadata — nothing cross-schema to resolve). Never hand-write a view model again.
|
|
53
53
|
- **Secrets battery** (`matrx_orm.secrets_battery`): the ONE canonical user-credential vault — `users.user_secrets` CRUD + the platform's single Fernet primitive, keyed by `CREDENTIALS_ENCRYPTION_KEY`. Any package: `configure_secrets(db_config_name=...)` once, then `await require_user_secret(user_id, key)`. No package ever calls another service to resolve a credential. Contract: [`matrx_orm/secrets_battery/FEATURE.md`](matrx_orm/secrets_battery/FEATURE.md).
|
|
54
54
|
- **Polymorphic associations** (`matrx_orm.associations`): `EntityRegistry` (token→Model) + `Associations` — a registry-driven, **access-gated** edge engine over a host's polymorphic edge table. Every `source→target` hop is org-scoped *and* each resolved endpoint is re-authorized against the ACTOR (fail-closed, screams on a genuine leak), so a hop can't widen access past what the actor may reach. 🚨 **The gate is `access_check` (wire `matrx_orm.platform_access_check` → `iam.has_access_for`), NEVER an org-column comparison** — a person belongs to several orgs on purpose, so org-equality denies her own data across her own workspaces (2026-08-30 incident) while granting any co-member of a private row. The edge's `org_id` is a TENANCY stamp, not a permission. Org-equality survives only for UNATTENDED writes (no actor) as an integrity guard, and **system vocabulary is exempt entirely** (existence still required): a model generated with `_rls_variant="system"` is platform-shared dictionary data (seo.keyword/topic) every tenant legitimately links to. Hardcodes no table names — the host wires its own edge + token-registry models.
|
|
55
|
-
- **The host's declared session context** (`matrx_orm.core.session_context`): `configure_session_context(provider)` once at startup, and every **top-level** `transaction()` — including the one `rls_session` opens, before it drops to the restricted role — emits whatever the provider returns as transaction-local GUCs. This is how an application-level declaration reaches Postgres at all: a GUC set outside a transaction has no meaning, so the BEGIN is the only place it can land. aidream wires it to its provenance declaration (`app.actor_tier` / `app.actor_system`, read by `platform.actor_tier()`, the history capture and the tier rule). A nested entry is a savepoint inside a transaction that already carries them and emits nothing
|
|
55
|
+
- **The host's declared session context** (`matrx_orm.core.session_context`): `configure_session_context(provider)` once at startup, and every **top-level** `transaction()` — including the one `rls_session` opens, before it drops to the restricted role — emits whatever the provider returns as transaction-local GUCs. This is how an application-level declaration reaches Postgres at all: a GUC set outside a transaction has no meaning, so the BEGIN is the only place it can land. aidream wires it to its provenance declaration (`app.actor_tier` / `app.actor_system`, read by `platform.actor_tier()`, the history capture and the tier rule). A nested entry is a savepoint inside a transaction that already carries them and emits nothing. A standalone write (`execute_write` outside any transaction) made while the provider has something to say opens its own transaction for that one statement — and still goes through the write queue, inside a copy of the caller's context, so the queue's concurrency gate is kept and the drain task's foreign context cannot hide the declaration (`tests/level1/test_standalone_write_session_context.py`). No provider configured ⇒ no GUCs (standalone behaviour unchanged); a provider that raises fails the transaction loudly rather than committing rows with a false provenance.
|
|
56
56
|
- **The platform access policy** (`matrx_orm.platform_access`): `platform_access_check(actor, token, id, level)` → `iam.has_access_for`. The ONE answer to "may this actor touch this row?", shared with RLS and the file-access gate. Fails closed. Never re-derive it from `organization_id` or `created_by` — both are strict subsets that deny legitimate access.
|
|
57
57
|
|
|
58
58
|
Public API is defined explicitly in `matrx_orm/__init__.py` (100+ exports). Treat changes there as semver-breaking.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: matrx-orm
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.142
|
|
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
|
|
@@ -25,7 +25,7 @@ Requires-Python: >=3.13
|
|
|
25
25
|
Requires-Dist: asyncpg>=0.31.0
|
|
26
26
|
Requires-Dist: cryptography>=43.0
|
|
27
27
|
Requires-Dist: gitpython>=3.1.40
|
|
28
|
-
Requires-Dist: matrx-utils>=2.0.
|
|
28
|
+
Requires-Dist: matrx-utils>=2.0.38
|
|
29
29
|
Requires-Dist: psycopg-pool>=3.2.5
|
|
30
30
|
Requires-Dist: psycopg[binary]>=3.2.5
|
|
31
31
|
Requires-Dist: python-dotenv>=1.0
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
+
import contextvars
|
|
4
5
|
from dataclasses import dataclass
|
|
5
6
|
import json
|
|
6
7
|
import logging
|
|
@@ -1605,25 +1606,46 @@ class AsyncDatabaseManager:
|
|
|
1605
1606
|
except ImportError:
|
|
1606
1607
|
pass
|
|
1607
1608
|
|
|
1609
|
+
from matrx_orm.core.write_queue import get_write_queue_registry
|
|
1610
|
+
|
|
1611
|
+
config_name = resolve_database_name(config_name)
|
|
1612
|
+
wq = get_write_queue_registry().get(config_name)
|
|
1613
|
+
|
|
1608
1614
|
# A declared host session context (notably actor provenance) is made of
|
|
1609
1615
|
# transaction-local GUCs. A plain standalone write used to skip BEGIN,
|
|
1610
1616
|
# so its trigger saw the database default even though the caller had
|
|
1611
|
-
# explicitly declared an actor
|
|
1612
|
-
#
|
|
1613
|
-
#
|
|
1617
|
+
# explicitly declared an actor — so a declared write takes the
|
|
1618
|
+
# canonical transaction path (BEGIN → GUCs → statement → COMMIT).
|
|
1619
|
+
#
|
|
1620
|
+
# It still goes through the write queue. The queue's drain executes
|
|
1621
|
+
# each item in a task forked from the DRAIN's context, where the
|
|
1622
|
+
# caller's ContextVar declaration is invisible; so the item is run
|
|
1623
|
+
# inside a copy of the caller's context instead of skipping the
|
|
1624
|
+
# queue. Skipping was the first fix (2026-09-12) and it was fine while
|
|
1625
|
+
# only a few doors declared; once the request boundary declares for
|
|
1626
|
+
# EVERY authenticated request, skipping would have moved every
|
|
1627
|
+
# request write off the queue's concurrency gate and onto raw pool
|
|
1628
|
+
# acquires — the pool-storm class the queue exists to prevent.
|
|
1614
1629
|
from matrx_orm.core.session_context import session_context_gucs
|
|
1615
1630
|
|
|
1616
1631
|
if session_context_gucs():
|
|
1617
1632
|
from matrx_orm.core.transaction import transaction
|
|
1618
1633
|
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1634
|
+
async def _declared_write() -> list[dict[str, Any]]:
|
|
1635
|
+
async with transaction(config_name):
|
|
1636
|
+
return await cls.execute_query(config_name, query, *args, timeout=timeout)
|
|
1622
1637
|
|
|
1623
|
-
|
|
1638
|
+
if wq is None:
|
|
1639
|
+
return await _declared_write()
|
|
1624
1640
|
|
|
1625
|
-
|
|
1626
|
-
|
|
1641
|
+
caller_context = contextvars.copy_context()
|
|
1642
|
+
|
|
1643
|
+
async def _in_caller_context() -> list[dict[str, Any]]:
|
|
1644
|
+
# Awaited by the drain's item task; the inner task carries the
|
|
1645
|
+
# caller's context so the session provider sees the declaration.
|
|
1646
|
+
return await asyncio.create_task(_declared_write(), context=caller_context)
|
|
1647
|
+
|
|
1648
|
+
return await wq.submit(_in_caller_context)
|
|
1627
1649
|
|
|
1628
1650
|
if wq is not None:
|
|
1629
1651
|
return await wq.submit(
|
|
@@ -47,6 +47,16 @@ artifacts, not general document storage.
|
|
|
47
47
|
`editable=false` blocks human value changes (`FieldNotEditableError`); the owning
|
|
48
48
|
service rotates via `system_rotation=True` / `rotate_credential_fields`.
|
|
49
49
|
|
|
50
|
+
**Protected execution purpose.** `execution_purpose` is a closed, immutable
|
|
51
|
+
row purpose. `passkey_private` is reserved for native-provider use and is
|
|
52
|
+
never creatable through generic battery or HTTP constructors. Generic
|
|
53
|
+
single-field resolution refuses it before decrypt with the stable
|
|
54
|
+
`protected_execution_required` code; bulk env/token resolution omits it.
|
|
55
|
+
Item metadata and grants remain metadata-only, while generic child mutation,
|
|
56
|
+
fork, and transfer refuse a protected child. Every competing child/lifecycle
|
|
57
|
+
mutation locks and re-reads the parent in its transaction; whole-item delete
|
|
58
|
+
remains atomic.
|
|
59
|
+
|
|
50
60
|
**`inject_into_sandbox=true` REQUIRES an env alias — refused otherwise.** A
|
|
51
61
|
container environment is a NAME→value map, so a field with `key IS NULL` has
|
|
52
62
|
nowhere to land and bulk resolution drops it. Accepting the flag anyway meant a
|
|
@@ -76,6 +76,7 @@ from .items import (
|
|
|
76
76
|
CredentialItemNotFoundError,
|
|
77
77
|
CredentialItemRow,
|
|
78
78
|
FieldNotEditableError,
|
|
79
|
+
ProtectedSecretPurposeError,
|
|
79
80
|
FieldSpec,
|
|
80
81
|
ItemGrantSpec,
|
|
81
82
|
SandboxInjectionWithoutEnvKeyError,
|
|
@@ -210,6 +211,7 @@ __all__ = [
|
|
|
210
211
|
"CredentialItemNotFoundError",
|
|
211
212
|
"CredentialItemRow",
|
|
212
213
|
"FieldNotEditableError",
|
|
214
|
+
"ProtectedSecretPurposeError",
|
|
213
215
|
"FieldSpec",
|
|
214
216
|
"ItemGrantSpec",
|
|
215
217
|
"SandboxInjectionWithoutEnvKeyError",
|
|
@@ -67,6 +67,9 @@ def register_non_transferable_field_key(field_key: str) -> None:
|
|
|
67
67
|
|
|
68
68
|
|
|
69
69
|
HANDLING_MODES: tuple[str, ...] = ("visible", "revealable", "sealed")
|
|
70
|
+
GENERAL_EXECUTION_PURPOSE = "general"
|
|
71
|
+
PASSKEY_PRIVATE_EXECUTION_PURPOSE = "passkey_private"
|
|
72
|
+
PROTECTED_FIELD_KEY = "passkey_private"
|
|
70
73
|
ITEM_STATUSES: tuple[str, ...] = ("active", "disabled", "expired", "pending_verification")
|
|
71
74
|
URI_MATCH_MODES: tuple[str, ...] = ("host", "domain", "exact", "never")
|
|
72
75
|
ITEM_SOURCES: tuple[str, ...] = (
|
|
@@ -107,6 +110,24 @@ class SandboxInjectionWithoutEnvKeyError(ValueError):
|
|
|
107
110
|
together) or leave injection off."""
|
|
108
111
|
|
|
109
112
|
|
|
113
|
+
class ProtectedSecretPurposeError(PermissionError):
|
|
114
|
+
"""A generic path reached a native-provider-only secret before decrypt."""
|
|
115
|
+
|
|
116
|
+
code = "protected_execution_required"
|
|
117
|
+
|
|
118
|
+
def __init__(self, *, field_id: str | None, credential_item_id: str | None) -> None:
|
|
119
|
+
self.field_id = field_id
|
|
120
|
+
self.credential_item_id = credential_item_id
|
|
121
|
+
super().__init__(self.code)
|
|
122
|
+
|
|
123
|
+
def metadata(self) -> dict[str, str | None]:
|
|
124
|
+
return {
|
|
125
|
+
"code": self.code,
|
|
126
|
+
"field_id": self.field_id,
|
|
127
|
+
"credential_item_id": self.credential_item_id,
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
|
|
110
131
|
# ── Shapes ────────────────────────────────────────────────────────────────
|
|
111
132
|
|
|
112
133
|
|
|
@@ -153,6 +174,7 @@ class CredentialFieldRow:
|
|
|
153
174
|
description: str | None
|
|
154
175
|
created_at: str
|
|
155
176
|
updated_at: str
|
|
177
|
+
execution_purpose: str
|
|
156
178
|
|
|
157
179
|
def to_dict(self) -> dict[str, Any]:
|
|
158
180
|
return dict(self.__dict__)
|
|
@@ -308,6 +330,7 @@ _FIELD_FIELDS = (
|
|
|
308
330
|
"id",
|
|
309
331
|
"credential_item_id",
|
|
310
332
|
"field_key",
|
|
333
|
+
"execution_purpose",
|
|
311
334
|
"key",
|
|
312
335
|
"handling",
|
|
313
336
|
"editable",
|
|
@@ -354,6 +377,7 @@ def _field_row(raw: dict[str, Any]) -> CredentialFieldRow:
|
|
|
354
377
|
id=str(raw["id"]),
|
|
355
378
|
credential_item_id=str(raw["credential_item_id"]),
|
|
356
379
|
field_key=str(raw["field_key"] or ""),
|
|
380
|
+
execution_purpose=_execution_purpose(raw),
|
|
357
381
|
env_key=raw.get("key"),
|
|
358
382
|
handling=str(raw.get("handling") or "revealable"),
|
|
359
383
|
editable=bool(raw.get("editable", True)),
|
|
@@ -388,6 +412,47 @@ async def _require_item_raw(item_id: str) -> dict[str, Any]:
|
|
|
388
412
|
return rows[0]
|
|
389
413
|
|
|
390
414
|
|
|
415
|
+
async def _lock_item_raw(item_id: str) -> dict[str, Any]:
|
|
416
|
+
"""Lock and re-read a live parent within the current transaction."""
|
|
417
|
+
rows = await (
|
|
418
|
+
CredentialItem.filter(id=item_id, deleted_at__isnull=True)
|
|
419
|
+
.select_for_update()
|
|
420
|
+
.values(*_ITEM_FIELDS)
|
|
421
|
+
)
|
|
422
|
+
if not rows:
|
|
423
|
+
raise CredentialItemNotFoundError(f"credential item {item_id} not found")
|
|
424
|
+
return rows[0]
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def _execution_purpose(raw: dict[str, Any]) -> str:
|
|
428
|
+
purpose = raw.get("execution_purpose")
|
|
429
|
+
if purpose not in (GENERAL_EXECUTION_PURPOSE, PASSKEY_PRIVATE_EXECUTION_PURPOSE):
|
|
430
|
+
# A missing projection is a security-contract failure, never evidence
|
|
431
|
+
# that the row is ordinary.
|
|
432
|
+
raise ProtectedSecretPurposeError(
|
|
433
|
+
field_id=str(raw["id"]) if raw.get("id") else None,
|
|
434
|
+
credential_item_id=(str(raw["credential_item_id"]) if raw.get("credential_item_id") else None),
|
|
435
|
+
)
|
|
436
|
+
return str(purpose)
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
def _is_protected(raw: dict[str, Any]) -> bool:
|
|
440
|
+
return _execution_purpose(raw) == PASSKEY_PRIVATE_EXECUTION_PURPOSE
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def _refuse_protected(raw: dict[str, Any]) -> None:
|
|
444
|
+
if _is_protected(raw):
|
|
445
|
+
raise ProtectedSecretPurposeError(
|
|
446
|
+
field_id=str(raw["id"]) if raw.get("id") else None,
|
|
447
|
+
credential_item_id=(str(raw["credential_item_id"]) if raw.get("credential_item_id") else None),
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def _refuse_reserved_generic_field(field_key: str) -> None:
|
|
452
|
+
if field_key == PROTECTED_FIELD_KEY:
|
|
453
|
+
raise ValueError("field_key 'passkey_private' is reserved for native provider execution")
|
|
454
|
+
|
|
455
|
+
|
|
391
456
|
# ── Item CRUD ─────────────────────────────────────────────────────────────
|
|
392
457
|
|
|
393
458
|
|
|
@@ -516,6 +581,7 @@ async def create_credential_bundle(
|
|
|
516
581
|
seen: set[str] = set()
|
|
517
582
|
for spec in fields:
|
|
518
583
|
_validate_field_key(spec.field_key)
|
|
584
|
+
_refuse_reserved_generic_field(spec.field_key)
|
|
519
585
|
_validate_handling(spec.handling)
|
|
520
586
|
_validate_sandbox_injectable(
|
|
521
587
|
field_key=spec.field_key,
|
|
@@ -693,9 +759,9 @@ async def delete_credential_item(
|
|
|
693
759
|
) -> None:
|
|
694
760
|
"""Soft-delete the item, all its fields, and drop its grants — atomically.
|
|
695
761
|
Resolution stops immediately; ciphertext is recoverable until purge."""
|
|
696
|
-
raw = await _require_item_raw(item_id)
|
|
697
762
|
now = datetime.now(UTC)
|
|
698
763
|
async with transaction(PACKAGE_DB_NAME):
|
|
764
|
+
raw = await _lock_item_raw(item_id)
|
|
699
765
|
from .attachments import soft_delete_item_attachments
|
|
700
766
|
|
|
701
767
|
await UserSecretGrant.delete_where(credential_item_id=item_id)
|
|
@@ -743,6 +809,7 @@ async def add_credential_field(
|
|
|
743
809
|
first, then write.
|
|
744
810
|
"""
|
|
745
811
|
_validate_field_key(spec.field_key)
|
|
812
|
+
_refuse_reserved_generic_field(spec.field_key)
|
|
746
813
|
_validate_handling(spec.handling)
|
|
747
814
|
if spec.handling == "sealed":
|
|
748
815
|
await require_host_authorization(
|
|
@@ -758,30 +825,31 @@ async def add_credential_field(
|
|
|
758
825
|
)
|
|
759
826
|
if not isinstance(spec.value, str):
|
|
760
827
|
raise ValueError("value must be a string")
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
828
|
+
async with transaction(PACKAGE_DB_NAME):
|
|
829
|
+
raw = await _lock_item_raw(item_id)
|
|
830
|
+
instance = await UserSecret.create(
|
|
831
|
+
user_id=raw.get("user_id"),
|
|
832
|
+
organization_id=raw.get("organization_id"),
|
|
833
|
+
credential_item_id=item_id,
|
|
834
|
+
field_key=spec.field_key,
|
|
835
|
+
key=spec.env_key,
|
|
836
|
+
value_encrypted=encrypt_value(spec.value),
|
|
837
|
+
value_hint=mask_value(spec.value),
|
|
838
|
+
handling=spec.handling,
|
|
839
|
+
editable=spec.editable,
|
|
840
|
+
inject_into_sandbox=spec.inject_into_sandbox,
|
|
841
|
+
description=spec.description,
|
|
842
|
+
access_mode=str(raw.get("access_mode") or "all_members"),
|
|
843
|
+
created_by=actor_id,
|
|
844
|
+
updated_by=actor_id,
|
|
845
|
+
)
|
|
846
|
+
await record_secret_audit(
|
|
847
|
+
action="field_added",
|
|
848
|
+
actor_id=actor_id,
|
|
849
|
+
user_secret_id=str(instance.id),
|
|
850
|
+
organization_id=audit_organization_id,
|
|
851
|
+
metadata={"credential_item_id": item_id, "field_key": spec.field_key},
|
|
852
|
+
)
|
|
785
853
|
rows = await UserSecret.filter(id=instance.id).values(*_FIELD_FIELDS)
|
|
786
854
|
return _field_row(rows[0])
|
|
787
855
|
|
|
@@ -800,22 +868,31 @@ async def update_credential_field_value(
|
|
|
800
868
|
token refresh, key rotation)."""
|
|
801
869
|
if not isinstance(value, str):
|
|
802
870
|
raise ValueError("value must be a string")
|
|
803
|
-
rows = await UserSecret.filter(id=field_id, deleted_at__isnull=True).values(
|
|
871
|
+
rows = await UserSecret.filter(id=field_id, deleted_at__isnull=True).values("credential_item_id")
|
|
804
872
|
if not rows:
|
|
805
873
|
raise SecretNotFoundError(f"credential field {field_id} not found")
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
874
|
+
async with transaction(PACKAGE_DB_NAME):
|
|
875
|
+
if rows[0].get("credential_item_id"):
|
|
876
|
+
await _lock_item_raw(str(rows[0]["credential_item_id"]))
|
|
877
|
+
rows = await UserSecret.filter(id=field_id, deleted_at__isnull=True).select_for_update().values(
|
|
878
|
+
*_FIELD_FIELDS
|
|
879
|
+
)
|
|
880
|
+
if not rows:
|
|
881
|
+
raise SecretNotFoundError(f"credential field {field_id} not found")
|
|
882
|
+
raw = rows[0]
|
|
883
|
+
_refuse_protected(raw)
|
|
884
|
+
if not bool(raw.get("editable", True)) and not system_rotation:
|
|
885
|
+
raise FieldNotEditableError(
|
|
886
|
+
f"field {raw.get('field_key') or raw.get('key')!r} is not editable — "
|
|
887
|
+
"it is managed by its owning integration"
|
|
888
|
+
)
|
|
889
|
+
await UserSecret.update_where(
|
|
890
|
+
{"id": field_id, "deleted_at__isnull": True},
|
|
891
|
+
value_encrypted=encrypt_value(value),
|
|
892
|
+
value_hint=mask_value(value),
|
|
893
|
+
updated_by=actor_id,
|
|
894
|
+
updated_at=datetime.now(UTC),
|
|
811
895
|
)
|
|
812
|
-
await UserSecret.update_where(
|
|
813
|
-
{"id": field_id, "deleted_at__isnull": True},
|
|
814
|
-
value_encrypted=encrypt_value(value),
|
|
815
|
-
value_hint=mask_value(value),
|
|
816
|
-
updated_by=actor_id,
|
|
817
|
-
updated_at=datetime.now(UTC),
|
|
818
|
-
)
|
|
819
896
|
await record_secret_audit(
|
|
820
897
|
action="rotated" if system_rotation else "field_updated",
|
|
821
898
|
actor_id=actor_id,
|
|
@@ -852,10 +929,13 @@ async def rotate_credential_fields(
|
|
|
852
929
|
async with transaction(PACKAGE_DB_NAME):
|
|
853
930
|
# Item + field liveness re-checked INSIDE the transaction so a
|
|
854
931
|
# concurrent delete can't race a half-rotation in.
|
|
855
|
-
await
|
|
932
|
+
await _lock_item_raw(item_id)
|
|
856
933
|
rows = await UserSecret.filter(
|
|
857
934
|
credential_item_id=item_id, field_key__in=list(new_values), deleted_at__isnull=True
|
|
858
|
-
).values("id", "field_key")
|
|
935
|
+
).values("id", "field_key", "credential_item_id", "execution_purpose")
|
|
936
|
+
protected = [row for row in rows if _is_protected(row)]
|
|
937
|
+
if protected:
|
|
938
|
+
_refuse_protected(protected[0])
|
|
859
939
|
found = {str(r["field_key"]): str(r["id"]) for r in rows}
|
|
860
940
|
missing = sorted(set(new_values) - set(found))
|
|
861
941
|
if missing:
|
|
@@ -916,12 +996,18 @@ async def update_field_metadata(
|
|
|
916
996
|
raise ValueError(f"env_key must match {VALID_KEY_RE}; got {env_key!r}")
|
|
917
997
|
|
|
918
998
|
async with transaction(PACKAGE_DB_NAME):
|
|
919
|
-
rows = await UserSecret.filter(id=field_id, deleted_at__isnull=True).values(
|
|
999
|
+
rows = await UserSecret.filter(id=field_id, deleted_at__isnull=True).values("credential_item_id")
|
|
1000
|
+
if not rows:
|
|
1001
|
+
raise SecretNotFoundError(f"credential field {field_id} not found")
|
|
1002
|
+
if rows[0].get("credential_item_id"):
|
|
1003
|
+
await _lock_item_raw(str(rows[0]["credential_item_id"]))
|
|
1004
|
+
rows = await UserSecret.filter(id=field_id, deleted_at__isnull=True).select_for_update().values(
|
|
920
1005
|
*_FIELD_FIELDS, "user_id", "organization_id"
|
|
921
1006
|
)
|
|
922
1007
|
if not rows:
|
|
923
1008
|
raise SecretNotFoundError(f"credential field {field_id} not found")
|
|
924
1009
|
raw = rows[0]
|
|
1010
|
+
_refuse_protected(raw)
|
|
925
1011
|
|
|
926
1012
|
if handling is not None and str(raw.get("handling")) == "sealed" and handling != "sealed":
|
|
927
1013
|
raise SealedFieldError(
|
|
@@ -1000,13 +1086,26 @@ async def delete_credential_field(
|
|
|
1000
1086
|
actor_id: str | None = None,
|
|
1001
1087
|
) -> bool:
|
|
1002
1088
|
"""Soft-delete one field. Returns True when a row was affected."""
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1089
|
+
rows = await UserSecret.filter(id=field_id, deleted_at__isnull=True).values("credential_item_id")
|
|
1090
|
+
if not rows:
|
|
1091
|
+
return False
|
|
1092
|
+
async with transaction(PACKAGE_DB_NAME):
|
|
1093
|
+
if rows[0].get("credential_item_id"):
|
|
1094
|
+
await _lock_item_raw(str(rows[0]["credential_item_id"]))
|
|
1095
|
+
rows = await UserSecret.filter(id=field_id, deleted_at__isnull=True).select_for_update().values(
|
|
1096
|
+
"id", "credential_item_id", "execution_purpose"
|
|
1097
|
+
)
|
|
1098
|
+
if not rows:
|
|
1099
|
+
return False
|
|
1100
|
+
raw = rows[0]
|
|
1101
|
+
_refuse_protected(raw)
|
|
1102
|
+
result = await UserSecret.update_where(
|
|
1103
|
+
{"id": field_id, "deleted_at__isnull": True},
|
|
1104
|
+
deleted_at=datetime.now(UTC),
|
|
1105
|
+
is_active=False,
|
|
1106
|
+
updated_by=actor_id,
|
|
1107
|
+
updated_at=datetime.now(UTC),
|
|
1108
|
+
)
|
|
1010
1109
|
if result.rows_affected:
|
|
1011
1110
|
await record_secret_audit(
|
|
1012
1111
|
action="field_deleted",
|
|
@@ -1025,10 +1124,11 @@ async def resolve_field_by_id(field_id: str) -> str:
|
|
|
1025
1124
|
``sealed`` fields; never expose through a human-facing surface. The host
|
|
1026
1125
|
must have authorized the consumer before calling."""
|
|
1027
1126
|
rows = await UserSecret.filter(id=field_id, deleted_at__isnull=True, is_active=True).values(
|
|
1028
|
-
"id", "field_key", "key", "value_encrypted"
|
|
1127
|
+
"id", "credential_item_id", "field_key", "key", "execution_purpose", "value_encrypted"
|
|
1029
1128
|
)
|
|
1030
1129
|
if not rows:
|
|
1031
1130
|
raise SecretNotFoundError(f"credential field {field_id} not found")
|
|
1131
|
+
_refuse_protected(rows[0])
|
|
1032
1132
|
try:
|
|
1033
1133
|
value = decrypt_value(rows[0]["value_encrypted"])
|
|
1034
1134
|
except Exception as exc:
|
|
@@ -1048,9 +1148,10 @@ async def resolve_field(item_id: str, field_key: str) -> str:
|
|
|
1048
1148
|
field_key=field_key,
|
|
1049
1149
|
deleted_at__isnull=True,
|
|
1050
1150
|
is_active=True,
|
|
1051
|
-
).values("id")
|
|
1151
|
+
).values("id", "credential_item_id", "execution_purpose")
|
|
1052
1152
|
if not rows:
|
|
1053
1153
|
raise SecretNotFoundError(f"item {item_id} has no active field {field_key!r}")
|
|
1154
|
+
_refuse_protected(rows[0])
|
|
1054
1155
|
return await resolve_field_by_id(str(rows[0]["id"]))
|
|
1055
1156
|
|
|
1056
1157
|
|
|
@@ -1062,6 +1163,7 @@ async def resolve_item_fields(item_id: str, *, sandbox_only: bool = False) -> di
|
|
|
1062
1163
|
"credential_item_id": item_id,
|
|
1063
1164
|
"deleted_at__isnull": True,
|
|
1064
1165
|
"is_active": True,
|
|
1166
|
+
"execution_purpose": GENERAL_EXECUTION_PURPOSE,
|
|
1065
1167
|
}
|
|
1066
1168
|
if sandbox_only:
|
|
1067
1169
|
filters["inject_into_sandbox"] = True
|
|
@@ -1103,14 +1205,19 @@ async def reveal_field(
|
|
|
1103
1205
|
field_key=field_key,
|
|
1104
1206
|
deleted_at__isnull=True,
|
|
1105
1207
|
is_active=True,
|
|
1106
|
-
).values(
|
|
1208
|
+
).values(
|
|
1209
|
+
"id", "field_key", "key", "handling", "execution_purpose", "value_encrypted",
|
|
1210
|
+
"credential_item_id",
|
|
1211
|
+
)
|
|
1107
1212
|
else:
|
|
1108
1213
|
rows = await UserSecret.filter(id=field_id, deleted_at__isnull=True, is_active=True).values(
|
|
1109
|
-
"id", "field_key", "key", "handling", "
|
|
1214
|
+
"id", "field_key", "key", "handling", "execution_purpose", "value_encrypted",
|
|
1215
|
+
"credential_item_id",
|
|
1110
1216
|
)
|
|
1111
1217
|
if not rows:
|
|
1112
1218
|
raise SecretNotFoundError("credential field not found")
|
|
1113
1219
|
raw = rows[0]
|
|
1220
|
+
_refuse_protected(raw)
|
|
1114
1221
|
handling = str(raw.get("handling") or "revealable")
|
|
1115
1222
|
if handling == "sealed":
|
|
1116
1223
|
await record_secret_audit(
|
|
@@ -1463,11 +1570,18 @@ async def transfer_credential_item(
|
|
|
1463
1570
|
do NOT travel: they are retired in the same transaction and named in the
|
|
1464
1571
|
returned :class:`TransferResult`."""
|
|
1465
1572
|
_validate_owner(to_user_id, to_organization_id)
|
|
1466
|
-
raw = await _require_item_raw(item_id)
|
|
1467
|
-
from_user_id = str(raw["user_id"]) if raw.get("user_id") else None
|
|
1468
|
-
from_organization_id = str(raw["organization_id"]) if raw.get("organization_id") else None
|
|
1469
1573
|
now = datetime.now(UTC)
|
|
1470
1574
|
async with transaction(PACKAGE_DB_NAME):
|
|
1575
|
+
raw = await _lock_item_raw(item_id)
|
|
1576
|
+
protected = await UserSecret.filter(
|
|
1577
|
+
credential_item_id=item_id,
|
|
1578
|
+
deleted_at__isnull=True,
|
|
1579
|
+
execution_purpose=PASSKEY_PRIVATE_EXECUTION_PURPOSE,
|
|
1580
|
+
).values("id", "credential_item_id", "execution_purpose")
|
|
1581
|
+
if protected:
|
|
1582
|
+
_refuse_protected(protected[0])
|
|
1583
|
+
from_user_id = str(raw["user_id"]) if raw.get("user_id") else None
|
|
1584
|
+
from_organization_id = str(raw["organization_id"]) if raw.get("organization_id") else None
|
|
1471
1585
|
retired = await _retire_non_transferable_fields(
|
|
1472
1586
|
item_id, actor_id=actor_id, now=now, event="transfer"
|
|
1473
1587
|
)
|
|
@@ -1534,29 +1648,22 @@ async def fork_credential_item(
|
|
|
1534
1648
|
hands the copy to a principal who never established them (D-15). The new
|
|
1535
1649
|
item's ``lifecycle.fields_not_copied`` names what was left behind."""
|
|
1536
1650
|
_validate_owner(to_user_id, to_organization_id)
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1651
|
+
async with transaction(PACKAGE_DB_NAME):
|
|
1652
|
+
raw = await _lock_item_raw(item_id)
|
|
1653
|
+
all_field_rows = await UserSecret.filter(
|
|
1654
|
+
credential_item_id=item_id, deleted_at__isnull=True
|
|
1655
|
+
).values(*_FIELD_FIELDS, "value_encrypted")
|
|
1656
|
+
protected = [row for row in all_field_rows if _is_protected(row)]
|
|
1657
|
+
if protected:
|
|
1658
|
+
_refuse_protected(protected[0])
|
|
1659
|
+
field_rows = [
|
|
1660
|
+
fr for fr in all_field_rows if str(fr.get("field_key")) not in NON_TRANSFERABLE_FIELD_KEYS
|
|
1661
|
+
]
|
|
1662
|
+
not_copied = sorted(
|
|
1546
1663
|
str(fr.get("field_key"))
|
|
1547
1664
|
for fr in all_field_rows
|
|
1548
1665
|
if str(fr.get("field_key")) in NON_TRANSFERABLE_FIELD_KEYS
|
|
1549
|
-
}
|
|
1550
|
-
)
|
|
1551
|
-
if not_copied:
|
|
1552
|
-
logger.warning(
|
|
1553
|
-
"credential item %s: fork does NOT copy minting field(s) %s — the fork's "
|
|
1554
|
-
"owner must establish them again (D-15)",
|
|
1555
|
-
item_id,
|
|
1556
|
-
not_copied,
|
|
1557
1666
|
)
|
|
1558
|
-
|
|
1559
|
-
async with transaction(PACKAGE_DB_NAME):
|
|
1560
1667
|
new_item = await CredentialItem.create(
|
|
1561
1668
|
user_id=to_user_id,
|
|
1562
1669
|
organization_id=to_organization_id,
|
|
@@ -80,6 +80,7 @@ class UserSecret(Model):
|
|
|
80
80
|
source_user_secret_version = IntegerField()
|
|
81
81
|
credential_item_id = UUIDField()
|
|
82
82
|
field_key = TextField()
|
|
83
|
+
execution_purpose = TextField(null=False, default="general")
|
|
83
84
|
handling = TextField(null=False, default="revealable")
|
|
84
85
|
editable = BooleanField(null=False, default=True)
|
|
85
86
|
key = TextField()
|