matrx-orm 3.1.80__tar.gz → 3.1.82__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.
Files changed (346) hide show
  1. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/PKG-INFO +1 -1
  2. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/FEATURE.md +11 -0
  3. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/expressions.py +155 -40
  4. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/registry.py +10 -1
  5. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/secrets_battery/FEATURE.md +11 -3
  6. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/secrets_battery/org_service.py +25 -6
  7. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/pyproject.toml +1 -1
  8. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_expression_primitives.py +15 -14
  9. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_registry.py +2 -4
  10. matrx_orm-3.1.82/tests/level1/test_subquery_nested_expressions.py +167 -0
  11. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/test_secrets_battery.py +41 -0
  12. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/.gitignore +0 -0
  13. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/AGENTS.md +0 -0
  14. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/BASE_CLASS_METHODS.md +0 -0
  15. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/CLAUDE.md +0 -0
  16. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/MODEL_API.md +0 -0
  17. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/README.md +0 -0
  18. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/database/orm/extended/managers/ai_model_base.py +0 -0
  19. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/docs/migrations.md +0 -0
  20. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/FEATURE.md +0 -0
  21. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/MODULE_README.md +0 -0
  22. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/__init__.py +0 -0
  23. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/adapters/MODULE_README.md +0 -0
  24. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/adapters/__init__.py +0 -0
  25. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/adapters/async_postgresql.py +0 -0
  26. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/adapters/base_adapter.py +0 -0
  27. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/adapters/postgresql.py +0 -0
  28. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/adapters/postgrest_client_adapter.py +0 -0
  29. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/adapters/supabase_adapter.py +0 -0
  30. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/admin/FEATURE.md +0 -0
  31. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/admin/__init__.py +0 -0
  32. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/admin/router.py +0 -0
  33. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/api/__init__.py +0 -0
  34. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/api/auth.py +0 -0
  35. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/api/config.py +0 -0
  36. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/api/handlers.py +0 -0
  37. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/api/protocol.py +0 -0
  38. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/api/server.py +0 -0
  39. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/associations.py +0 -0
  40. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/cache_debug.py +0 -0
  41. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/catalog.py +0 -0
  42. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/catalog_sql.py +0 -0
  43. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/client/MODULE_README.md +0 -0
  44. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/client/__init__.py +0 -0
  45. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/client/postgres_connection.py +0 -0
  46. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/client/postgrest.py +0 -0
  47. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/client/supabase_auth.py +0 -0
  48. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/client/supabase_config.py +0 -0
  49. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/client/supabase_manager.py +0 -0
  50. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/EXTENDED-TASK.md +0 -0
  51. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/MODULE_README.md +0 -0
  52. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/RELATIONS-TASKS.md +0 -0
  53. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/TASKS.md +0 -0
  54. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/__init__.py +0 -0
  55. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/async_db_manager.py +0 -0
  56. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/base.py +0 -0
  57. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/config.py +0 -0
  58. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/connections.py +0 -0
  59. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/db_function.py +0 -0
  60. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/diagnostics.py +0 -0
  61. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/extended.py +0 -0
  62. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/fields.py +0 -0
  63. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/introspection.py +0 -0
  64. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/listen.py +0 -0
  65. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/loop_filters.py +0 -0
  66. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/model_dto.py +0 -0
  67. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/model_view.py +0 -0
  68. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/one_database.py +0 -0
  69. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/paginator.py +0 -0
  70. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/pool_maintenance.py +0 -0
  71. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/pool_watch.py +0 -0
  72. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/pooler_role.py +0 -0
  73. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/pydantic_bridge.py +0 -0
  74. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/relations.py +0 -0
  75. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/resilience.py +0 -0
  76. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/rls_session.py +0 -0
  77. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/signals.py +0 -0
  78. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/tls.py +0 -0
  79. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/transaction.py +0 -0
  80. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/types.py +0 -0
  81. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/core/write_queue.py +0 -0
  82. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/entity.py +0 -0
  83. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/error_handling.py +0 -0
  84. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/exceptions.py +0 -0
  85. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/extended/__init__.py +0 -0
  86. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/extended/app_error_handler.py +0 -0
  87. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/identity_rule.py +0 -0
  88. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/local_sqlite.py +0 -0
  89. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/middleware/__init__.py +0 -0
  90. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/middleware/base.py +0 -0
  91. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/migrations/MODULE_README.md +0 -0
  92. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/migrations/__init__.py +0 -0
  93. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/migrations/cli.py +0 -0
  94. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/migrations/ddl.py +0 -0
  95. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/migrations/diff.py +0 -0
  96. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/migrations/executor.py +0 -0
  97. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/migrations/integration.py +0 -0
  98. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/migrations/loader.py +0 -0
  99. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/migrations/operations.py +0 -0
  100. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/migrations/state.py +0 -0
  101. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/migrations/table_filter.py +0 -0
  102. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/operations/FEATURE.md +0 -0
  103. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/operations/MODULE_README.md +0 -0
  104. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/operations/__init__.py +0 -0
  105. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/operations/bulk_update_values.py +0 -0
  106. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/operations/conflict.py +0 -0
  107. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/operations/conflict_writes.py +0 -0
  108. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/operations/create.py +0 -0
  109. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/operations/db_functions.py +0 -0
  110. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/operations/delete.py +0 -0
  111. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/operations/dynamic_admin.py +0 -0
  112. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/operations/dynamic_crud.py +0 -0
  113. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/operations/graph_walk.py +0 -0
  114. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/operations/latest_rows.py +0 -0
  115. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/operations/queue_claim.py +0 -0
  116. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/operations/read.py +0 -0
  117. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/operations/relational_backfill.py +0 -0
  118. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/operations/staging_load.py +0 -0
  119. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/operations/update.py +0 -0
  120. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/platform_db.py +0 -0
  121. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/py.typed +0 -0
  122. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/python_sql/MODULE_README.md +0 -0
  123. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/python_sql/__init__.py +0 -0
  124. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/python_sql/db_objects.py +0 -0
  125. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/python_sql/table_detailed_relationships.py +0 -0
  126. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/python_sql/table_typescript_relationship.py +0 -0
  127. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/query/FEATURE.md +0 -0
  128. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/query/MODULE_README.md +0 -0
  129. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/query/__init__.py +0 -0
  130. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/query/builder.py +0 -0
  131. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/query/executor.py +0 -0
  132. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/schema_builder/FEATURE.md +0 -0
  133. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/schema_builder/MODULE_README.md +0 -0
  134. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/schema_builder/__init__.py +0 -0
  135. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/schema_builder/code_handler.py +0 -0
  136. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/schema_builder/codegen_writer.py +0 -0
  137. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/schema_builder/columns.py +0 -0
  138. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/schema_builder/common.py +0 -0
  139. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/schema_builder/diff_preview.py +0 -0
  140. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/schema_builder/entity_capabilities.py +0 -0
  141. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/schema_builder/generator.py +0 -0
  142. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/schema_builder/helpers/__init__.py +0 -0
  143. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/schema_builder/helpers/base_generators.py +0 -0
  144. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/schema_builder/helpers/entity_generators.py +0 -0
  145. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/schema_builder/helpers/git_checker.py +0 -0
  146. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/schema_builder/package_wiring.py +0 -0
  147. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/schema_builder/relationships.py +0 -0
  148. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/schema_builder/runner.py +0 -0
  149. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/schema_builder/schema.py +0 -0
  150. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/schema_builder/schema_manager.py +0 -0
  151. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/schema_builder/tables.py +0 -0
  152. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/schema_builder/views.py +0 -0
  153. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/secrets_battery/__init__.py +0 -0
  154. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/secrets_battery/attachments.py +0 -0
  155. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/secrets_battery/crypto.py +0 -0
  156. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/secrets_battery/items.py +0 -0
  157. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/secrets_battery/model.py +0 -0
  158. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/secrets_battery/service.py +0 -0
  159. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/session/README.md +0 -0
  160. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/session/__init__.py +0 -0
  161. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/session/coalesce.py +0 -0
  162. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/session/dag.py +0 -0
  163. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/session/errors.py +0 -0
  164. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/session/fallback.py +0 -0
  165. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/session/flush.py +0 -0
  166. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/session/lifecycle.py +0 -0
  167. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/session/managed.py +0 -0
  168. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/session/op.py +0 -0
  169. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/session/reads.py +0 -0
  170. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/session/session.py +0 -0
  171. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/session/telemetry.py +0 -0
  172. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/sql_executor/MODULE_README.md +0 -0
  173. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/sql_executor/__init__.py +0 -0
  174. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/sql_executor/executor.py +0 -0
  175. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/sql_executor/queries.py +0 -0
  176. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/sql_executor/registry.py +0 -0
  177. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/sql_executor/types.py +0 -0
  178. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/sql_executor/utils.py +0 -0
  179. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/state.py +0 -0
  180. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/utils/__init__.py +0 -0
  181. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/utils/sql_utils.py +0 -0
  182. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/matrx_orm/utils/type_converters.py +0 -0
  183. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/scripts/git-branches.sh +0 -0
  184. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/scripts/publish.sh +0 -0
  185. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/scripts/release.sh +0 -0
  186. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/MODULE_README.md +0 -0
  187. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/__init__.py +0 -0
  188. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/conftest.py +0 -0
  189. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/MODULE_README.md +0 -0
  190. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/__init__.py +0 -0
  191. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_admin_db_columns.py +0 -0
  192. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_admin_response_rendering.py +0 -0
  193. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_admin_search_negation.py +0 -0
  194. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_admin_search_sentinels.py +0 -0
  195. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_advisory_lock.py +0 -0
  196. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_agent_message.py +0 -0
  197. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_api_auth.py +0 -0
  198. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_api_config.py +0 -0
  199. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_api_handlers.py +0 -0
  200. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_api_protocol.py +0 -0
  201. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_array_agg_order_by.py +0 -0
  202. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_associations.py +0 -0
  203. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_bulk_hydration_offload.py +0 -0
  204. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_bulk_update_by_pk.py +0 -0
  205. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_bulk_upsert_increment_set_fields.py +0 -0
  206. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_cache_debug.py +0 -0
  207. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_call_function.py +0 -0
  208. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_case_when.py +0 -0
  209. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_catalog.py +0 -0
  210. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_composite_pk_filter.py +0 -0
  211. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_composite_pk_write_paths.py +0 -0
  212. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_config.py +0 -0
  213. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_conflict_target.py +0 -0
  214. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_conflict_writes.py +0 -0
  215. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_connection_codecs.py +0 -0
  216. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_connection_poison_guard.py +0 -0
  217. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_connection_primitives.py +0 -0
  218. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_count_composite_distinct.py +0 -0
  219. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_db_function_field.py +0 -0
  220. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_ddl_generator.py +0 -0
  221. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_derived_table.py +0 -0
  222. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_dirty_tracking.py +0 -0
  223. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_dynamic_crud.py +0 -0
  224. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_exceptions.py +0 -0
  225. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_execute_query_integrity_mapping.py +0 -0
  226. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_fields.py +0 -0
  227. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_filter_jsonb_agg_array_index.py +0 -0
  228. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_from_alias.py +0 -0
  229. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_fts.py +0 -0
  230. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_func_expression_args.py +0 -0
  231. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_graph_walk.py +0 -0
  232. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_having_annotate_alias.py +0 -0
  233. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_insert_ignore_and_admin_primitives.py +0 -0
  234. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_introspect_rls_policies.py +0 -0
  235. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_join_and_aggregate.py +0 -0
  236. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_latest_rows.py +0 -0
  237. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_listen_notify.py +0 -0
  238. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_local_sqlite.py +0 -0
  239. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_managed_write_guard.py +0 -0
  240. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_matrx_entity.py +0 -0
  241. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_migration_diff_types.py +0 -0
  242. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_migration_loader.py +0 -0
  243. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_model_instance.py +0 -0
  244. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_model_meta.py +0 -0
  245. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_optimistic_concurrency.py +0 -0
  246. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_order_by_expression.py +0 -0
  247. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_parameter_name_hydration.py +0 -0
  248. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_patch_jsonb_path.py +0 -0
  249. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_pool_holder_census.py +0 -0
  250. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_pool_loop_recreation.py +0 -0
  251. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_pool_maintenance.py +0 -0
  252. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_postgrest_filters.py +0 -0
  253. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_pydantic_bridge.py +0 -0
  254. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_query_builder.py +0 -0
  255. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_query_error_detail.py +0 -0
  256. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_query_executor_sql.py +0 -0
  257. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_query_timeout.py +0 -0
  258. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_querybuilder_clone_semantics.py +0 -0
  259. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_queue_claim.py +0 -0
  260. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_registry_multi_database.py +0 -0
  261. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_registry_multi_schema.py +0 -0
  262. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_relational_backfill.py +0 -0
  263. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_relations.py +0 -0
  264. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_resilience.py +0 -0
  265. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_rls_session_scope.py +0 -0
  266. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_schema_exists.py +0 -0
  267. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_session_advisory_lock.py +0 -0
  268. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_state_cache.py +0 -0
  269. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_subquery_filter_raw.py +0 -0
  270. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_subquery_in_filter.py +0 -0
  271. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_supabase_auth.py +0 -0
  272. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_supabase_config.py +0 -0
  273. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_tls.py +0 -0
  274. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_typed_join_projection.py +0 -0
  275. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_typed_predicates.py +0 -0
  276. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_update_case_expression.py +0 -0
  277. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_update_subquery.py +0 -0
  278. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_update_with_rebase.py +0 -0
  279. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_upsert_default_leak.py +0 -0
  280. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_upsert_with_conflict.py +0 -0
  281. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_values_jsonb_decode.py +0 -0
  282. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_watched_lifecycle.py +0 -0
  283. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level1/test_write_retry_semantics.py +0 -0
  284. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level2/MODULE_README.md +0 -0
  285. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level2/__init__.py +0 -0
  286. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level2/conftest.py +0 -0
  287. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level2/test_bulk_ops.py +0 -0
  288. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level2/test_cache_integration.py +0 -0
  289. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level2/test_crud.py +0 -0
  290. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level2/test_foreign_keys.py +0 -0
  291. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level2/test_m2m.py +0 -0
  292. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level2/test_manager.py +0 -0
  293. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level2/test_migrations_live.py +0 -0
  294. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level2/test_query_execution.py +0 -0
  295. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/level2/test_schema_diff.py +0 -0
  296. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/live_pooler_role.py +0 -0
  297. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/sample_project/.env.example +0 -0
  298. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/sample_project/README.md +0 -0
  299. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/sample_project/__init__.py +0 -0
  300. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/sample_project/generate.py +0 -0
  301. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/sample_project/generated/.gitkeep +0 -0
  302. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/sample_project/matrx_orm.yaml +0 -0
  303. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/sample_project/test_schema_generation.py +0 -0
  304. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/sample_project_desktop/.env.example +0 -0
  305. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/sample_project_desktop/README.md +0 -0
  306. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/sample_project_desktop/__init__.py +0 -0
  307. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/sample_project_desktop/client_example.py +0 -0
  308. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/sample_project_desktop/client_example.ts +0 -0
  309. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/sample_project_desktop/client_supabase_example.py +0 -0
  310. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/sample_project_desktop/server.py +0 -0
  311. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/schema/entity_tests.py +0 -0
  312. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/schema/test_base_generation.py +0 -0
  313. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/schema/test_composite_pk_fk_generation.py +0 -0
  314. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/schema/test_composite_unique_generation.py +0 -0
  315. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/schema/test_dto_identity_generation.py +0 -0
  316. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/schema/test_entity_capability_generation.py +0 -0
  317. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/schema/test_generate_schema.py +0 -0
  318. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/schema/test_junction_analysis.py +0 -0
  319. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/schema/test_multi_schema_output.py +0 -0
  320. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/schema/test_sql_expression_defaults.py +0 -0
  321. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/schema/test_table_relationships_query.py +0 -0
  322. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/schema/test_view_generation.py +0 -0
  323. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/schema_builder/test_array_default_parsing.py +0 -0
  324. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/schema_builder/test_focused_generation_plan.py +0 -0
  325. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/schema_builder/test_host_models_aggregator.py +0 -0
  326. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/session/__init__.py +0 -0
  327. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/session/conftest.py +0 -0
  328. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/session/test_cache_no_downgrade.py +0 -0
  329. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/session/test_capture_poison_proof.py +0 -0
  330. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/session/test_coalesce.py +0 -0
  331. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/session/test_dag.py +0 -0
  332. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/session/test_disk_spill_fallback.py +0 -0
  333. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/session/test_flush_individual_fallback.py +0 -0
  334. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/session/test_governed_write.py +0 -0
  335. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/session/test_managed.py +0 -0
  336. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/session/test_op_enqueue_site.py +0 -0
  337. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/session/test_reads.py +0 -0
  338. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/session/test_session.py +0 -0
  339. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/test_database_name_alias.py +0 -0
  340. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/test_db_functions.py +0 -0
  341. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/test_model_cls_refactor.py +0 -0
  342. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/test_no_blocking_asyncpg_tls.py +0 -0
  343. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/test_one_database.py +0 -0
  344. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/test_pooler_role_guard.py +0 -0
  345. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/test_sql_param_casts.py +0 -0
  346. {matrx_orm-3.1.80 → matrx_orm-3.1.82}/tests/test_sync_twins_guard.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: matrx-orm
3
- Version: 3.1.80
3
+ Version: 3.1.82
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,6 +25,15 @@ guard and ensures diagnostic sinks such as `public.app_log` and
25
25
  Pinned by `tests/level1/test_model_meta.py` and
26
26
  `tests/level1/test_managed_write_guard.py`.
27
27
 
28
+ **Every registered model is discoverable by its resolved table identity.**
29
+ `ModelRegistry` indexes `_meta.table_name`, including the snake-case default
30
+ derived by `ModelMeta`; `_table_name` is only the compatibility fallback. A
31
+ generated model therefore remains reachable through
32
+ `get_model_by_table_name(schema, table)` even when its class does not repeat the
33
+ resolved table name as a raw attribute.
34
+
35
+ Pinned by `tests/level1/test_registry.py`.
36
+
28
37
  ## Database execution errors
29
38
 
30
39
  `AsyncDatabaseManager.execute_query` must classify every PostgreSQL integrity
@@ -119,6 +128,8 @@ Verified against code: 2026-08-17.
119
128
 
120
129
  ## Change log
121
130
 
131
+ - 2026-08-18: Indexed ModelMeta-derived table names so generated models remain
132
+ discoverable by exact schema/table identity.
122
133
  - 2026-08-18: Preserved successful query results when asyncpg times out while
123
134
  resetting and discarding the released connection.
124
135
  - 2026-08-18: Let waiters reuse healthy connections released behind a slow
@@ -810,28 +810,133 @@ class Exists(Predicate):
810
810
  return f"Exists({self.queryset!r})"
811
811
 
812
812
 
813
- def _iter_subquery_conditions(filters: Any):
814
- """Flatten a ``_build_query()`` filters LIST into (key, value) pairs.
813
+ _SUBQUERY_COMPARISON_OPERATORS = {
814
+ "eq": "=",
815
+ "ne": "<>",
816
+ "gt": ">",
817
+ "gte": ">=",
818
+ "lt": "<",
819
+ "lte": "<=",
820
+ }
821
+
815
822
 
816
- Handles dict items and leaf Q items (the correlated-subquery case). A
817
- compound or negated Q in a subquery raises ``NotImplementedError`` — better a
818
- clear error than silently-wrong SQL.
823
+ def _render_subquery_condition(
824
+ field_name: str,
825
+ operator: str,
826
+ value: Any,
827
+ *,
828
+ current_table: str,
829
+ outer_table: str,
830
+ params: list[Any],
831
+ sub_params: list[Any],
832
+ ) -> str:
833
+ """Render ONE ``WHERE`` condition INSIDE a subquery, expression values included.
834
+
835
+ The inner query of a Subquery/Exists is a real query: its own filters may
836
+ carry a nested ``Subquery``/``Exists``/``Func`` value, exactly like the
837
+ outer builder's ``_build_condition_with_expr`` handles. Before this, the
838
+ inner renderer fell through to ``_build_condition``, which emitted
839
+ ``field = ANY($n)`` and bound the *expression object itself* as a
840
+ parameter — asyncpg then failed to encode it at execute time (the
841
+ aidream ``conversations`` search+provider failure).
842
+
843
+ Placeholder numbering: expression values render against ``params +
844
+ sub_params`` so their ``$n`` are already absolute, and the params they
845
+ append are folded back into ``sub_params`` in order.
819
846
  """
820
- for item in filters:
821
- if isinstance(item, dict):
822
- yield from item.items()
823
- continue
824
- kwargs = getattr(item, "kwargs", None)
825
- children = getattr(item, "children", None)
826
- if kwargs is not None and children is not None: # a Q node
827
- if children or getattr(item, "negated", False):
828
- raise NotImplementedError(
829
- "Subquery/Exists supports only simple keyword filters in the "
830
- "inner query (compound/negated Q is not yet supported)."
847
+ from matrx_orm.query.executor import _SKIP_PARAM, QueryExecutor
848
+
849
+ if isinstance(value, OuterRef):
850
+ return f"{field_name} = {outer_table}.{value.field_name}"
851
+
852
+ if isinstance(value, Subquery | Exists | Func):
853
+ combined = [*params, *sub_params]
854
+ already_bound = len(combined)
855
+ if isinstance(value, Func):
856
+ expr_sql = value.as_sql(combined)
857
+ sql_operator = _SUBQUERY_COMPARISON_OPERATORS.get(operator)
858
+ if sql_operator is None:
859
+ raise ValueError(
860
+ f"Lookup {operator!r} is not supported with a database expression"
831
861
  )
832
- yield from kwargs.items()
833
- continue
834
- raise TypeError(f"Unexpected subquery filter item: {type(item)!r}")
862
+ condition = f"{field_name} {sql_operator} {expr_sql}"
863
+ elif isinstance(value, Exists):
864
+ condition = value.as_sql(
865
+ combined,
866
+ current_table=current_table,
867
+ outer_table=outer_table,
868
+ )
869
+ else:
870
+ sub_sql = value.as_sql(current_table, combined)
871
+ condition = (
872
+ f"{field_name} IN {sub_sql}"
873
+ if operator == "in"
874
+ else f"{field_name} = {sub_sql}"
875
+ )
876
+ sub_params.extend(combined[already_bound:])
877
+ return condition
878
+
879
+ cond, param = QueryExecutor._build_condition(
880
+ field_name, operator, value, len(params) + len(sub_params) + 1
881
+ )
882
+ if param is not _SKIP_PARAM:
883
+ # json_key with (key, expected) maps to two placeholders.
884
+ if isinstance(param, tuple):
885
+ sub_params.extend(param)
886
+ else:
887
+ sub_params.append(param)
888
+ return cond
889
+
890
+
891
+ def _render_subquery_q(
892
+ q: Any,
893
+ *,
894
+ current_table: str,
895
+ outer_table: str,
896
+ params: list[Any],
897
+ sub_params: list[Any],
898
+ ) -> str:
899
+ """Render a Q tree (compound / negated included) inside a subquery."""
900
+ if q.is_leaf:
901
+ conditions = []
902
+ for key, value in q.kwargs.items():
903
+ field_name, operator = _parse_lookup_simple(key)
904
+ conditions.append(
905
+ _render_subquery_condition(
906
+ F(field_name).field_name,
907
+ operator,
908
+ value,
909
+ current_table=current_table,
910
+ outer_table=outer_table,
911
+ params=params,
912
+ sub_params=sub_params,
913
+ )
914
+ )
915
+ sql = " AND ".join(c for c in conditions if c)
916
+ else:
917
+ child_sqls = [
918
+ _render_subquery_q(
919
+ child,
920
+ current_table=current_table,
921
+ outer_table=outer_table,
922
+ params=params,
923
+ sub_params=sub_params,
924
+ )
925
+ for child in q.children
926
+ ]
927
+ child_sqls = [c for c in child_sqls if c]
928
+ if not child_sqls:
929
+ return ""
930
+ if q.connector not in {"AND", "OR"}:
931
+ raise ValueError(f"Unsupported Q connector: {q.connector!r}")
932
+ sql = (
933
+ child_sqls[0]
934
+ if len(child_sqls) == 1
935
+ else "(" + f" {q.connector} ".join(child_sqls) + ")"
936
+ )
937
+ if sql and q.negated:
938
+ sql = f"NOT ({sql})"
939
+ return sql
835
940
 
836
941
 
837
942
  def _render_subquery(
@@ -875,10 +980,11 @@ def _render_subquery(
875
980
 
876
981
  # ``_build_query()`` returns ``filters`` as a LIST of dict / Q items (never a
877
982
  # dict) — iterating it as a dict silently broke every correlated Subquery/
878
- # Exists. Flatten the simple-keyword items (the correlated-subquery case);
879
- # compound/negated Q in a subquery raises rather than emit wrong SQL.
983
+ # Exists. Each item is rendered IN ORDER so placeholder numbers and the
984
+ # params appended to ``sub_params`` stay in lockstep. The inner query is a
985
+ # real query: compound/negated Q and nested Subquery/Exists/Func values all
986
+ # render here, rather than raising or binding an expression as a parameter.
880
987
  filter_items = query_dict.get("filters", [])
881
- ordinary_filter_items: list[Any] = []
882
988
  current_table = query_dict.get("from_alias") or table
883
989
  outer_param_count = len(params)
884
990
  for item in filter_items:
@@ -889,25 +995,34 @@ def _render_subquery(
889
995
  outer_table=outer_table,
890
996
  )
891
997
  where_conditions.append(_shift_param_refs(predicate_sql, outer_param_count))
892
- else:
893
- ordinary_filter_items.append(item)
894
-
895
- for key, value in _iter_subquery_conditions(ordinary_filter_items):
896
- field_name, operator = _parse_lookup_simple(key)
897
- field_name = F(field_name).field_name
898
- # Resolve OuterRef → outer_table.field
899
- if isinstance(value, OuterRef):
900
- condition = f"{field_name} = {outer_table}.{value.field_name}"
901
- where_conditions.append(condition)
902
998
  continue
903
- from matrx_orm.query.executor import _SKIP_PARAM, QueryExecutor
904
-
905
- cond, param = QueryExecutor._build_condition(
906
- field_name, operator, value, len(params) + len(sub_params) + 1
907
- )
908
- where_conditions.append(cond)
909
- if param is not _SKIP_PARAM:
910
- sub_params.append(param)
999
+ if isinstance(item, dict):
1000
+ for key, value in item.items():
1001
+ field_name, operator = _parse_lookup_simple(key)
1002
+ where_conditions.append(
1003
+ _render_subquery_condition(
1004
+ F(field_name).field_name,
1005
+ operator,
1006
+ value,
1007
+ current_table=current_table,
1008
+ outer_table=outer_table,
1009
+ params=params,
1010
+ sub_params=sub_params,
1011
+ )
1012
+ )
1013
+ continue
1014
+ if isinstance(item, Q):
1015
+ rendered = _render_subquery_q(
1016
+ item,
1017
+ current_table=current_table,
1018
+ outer_table=outer_table,
1019
+ params=params,
1020
+ sub_params=sub_params,
1021
+ )
1022
+ if rendered:
1023
+ where_conditions.append(rendered)
1024
+ continue
1025
+ raise TypeError(f"Unexpected subquery filter item: {type(item)!r}")
911
1026
 
912
1027
  # Explicit JOINs (``.join()`` on the INNER queryset) — the correlated-rollup
913
1028
  # pattern often needs to count/aggregate through a join (e.g. "how many of
@@ -48,6 +48,15 @@ def _database_of(model_class: type[Model]) -> str:
48
48
  return raw
49
49
 
50
50
 
51
+ def _table_of(model_class: type[Model]) -> str | None:
52
+ """The canonical table name, including ModelMeta-derived defaults."""
53
+ meta = getattr(model_class, "_meta", None)
54
+ table = getattr(meta, "table_name", None) if meta is not None else None
55
+ if not table:
56
+ table = getattr(model_class, "_table_name", None)
57
+ return table or None
58
+
59
+
51
60
  class ModelRegistry:
52
61
  """Global model registry keyed on ``(database, schema, class_name)``.
53
62
 
@@ -88,7 +97,7 @@ class ModelRegistry:
88
97
  )
89
98
  cls._models[key] = model_class
90
99
  cls._by_name.setdefault(name, {})[(database, schema)] = model_class
91
- table_name = getattr(model_class, "_table_name", None)
100
+ table_name = _table_of(model_class)
92
101
  if table_name:
93
102
  table_key = (database, schema, table_name)
94
103
  table_existing = cls._by_table.get(table_key)
@@ -174,6 +174,8 @@ A restricted secret is readable only by a user holding a `can_use` row in
174
174
  flag the host computes and passes down. Since Phase 1/2 the ITEM is the sharing
175
175
  unit: grants carry `credential_item_id` (field-level `user_secret_id` grants
176
176
  remain readable during migration; a CHECK requires at least one target).
177
+ Bulk restricted-org resolution accepts either the canonical item-level grant
178
+ or a legacy field-level grant; new writers create item-level grants only.
177
179
  `set_item_grants` takes per-grantee `ItemGrantSpec {user_id, can_use,
178
180
  can_manage}` — `can_manage` = edit + reveal-revealable, never
179
181
  share/transfer/delete — and writes ITEM-level rows only (exactly one target
@@ -213,8 +215,9 @@ losing the trail must not also lose the work.
213
215
 
214
216
  ## Key rotation
215
217
 
216
- `scripts/rotate_credentials_key.py` (aidream) re-encrypts every row from an OLD
217
- key to a NEW one: batched keyset pagination, per-row verify-before-commit,
218
+ `scripts/rotate_credentials_key.py` (aidream) re-encrypts every field and
219
+ protected-file attachment from an OLD key to a NEW one: batched keyset
220
+ pagination, per-row verify-before-commit,
218
221
  naturally resumable (already-under-new-key rows are skipped), state-file
219
222
  checkpoints with key-fingerprint guards, `--dry-run`, and symmetric rollback
220
223
  (re-run with the keys swapped). It builds its Fernets ONLY through this
@@ -248,7 +251,8 @@ contributed org copies show `out_of_sync` afterward (cosmetic).
248
251
  resolution — a broken row must not deny every other key.
249
252
 
250
253
  Drift guard: `scripts/validate_secrets_decrypt.py` (loud, non-blocking, in
251
- `release.sh`) asserts every active row still decrypts with the configured key.
254
+ `release.sh`) asserts every active field and attachment still decrypts with the
255
+ configured key.
252
256
 
253
257
  Verified against live code + the live `users.user_secrets` /
254
258
  `users.credential_items` schema 2026-07-23 (item-layer smoke run live: bundle
@@ -256,6 +260,10 @@ create, reveal, sealed denial, editable gate, rotate, item grants, soft delete).
256
260
 
257
261
  ## Change Log
258
262
 
263
+ - **2026-08-18** — Extended key rotation and decrypt-drift validation across
264
+ protected-file attachment ciphertext, with field-and-file regression proof.
265
+ - **2026-08-18** — Made restricted organization bulk resolution honor canonical
266
+ item-level grants while retaining legacy field-grant reads.
259
267
  - **2026-08-11** — Added first-class encrypted credential-file attachments with
260
268
  parent-owned access/lifecycle, a 25 MiB cap, one-way sealing, audited human
261
269
  download, trusted execution resolution, and fork/delete inheritance.
@@ -524,25 +524,44 @@ async def resolve_org_secrets(
524
524
  filters["key__isnull"] = False
525
525
  if keys:
526
526
  filters["key__in"] = keys
527
- rows = await UserSecret.filter(**filters).values("id", "key", "value_encrypted", "access_mode")
527
+ rows = await UserSecret.filter(**filters).values(
528
+ "id", "credential_item_id", "key", "value_encrypted", "access_mode"
529
+ )
528
530
  if for_sandbox:
529
531
  rows = _keyed_for_sandbox(rows, scope=f"organization {organization_id}")
530
532
  if not rows:
531
533
  return {}
532
534
 
533
- restricted_ids = [str(r["id"]) for r in rows if r.get("access_mode") == "restricted"]
534
- granted: set[str] = set()
535
+ restricted_rows = [r for r in rows if r.get("access_mode") == "restricted"]
536
+ restricted_ids = [str(r["id"]) for r in restricted_rows]
537
+ restricted_item_ids = sorted(
538
+ {str(r["credential_item_id"]) for r in restricted_rows if r.get("credential_item_id")}
539
+ )
540
+ granted_field_ids: set[str] = set()
541
+ granted_item_ids: set[str] = set()
535
542
  if restricted_ids and user_id and not is_org_admin:
536
- grant_rows = await UserSecretGrant.filter(
543
+ field_grants = await UserSecretGrant.filter(
537
544
  user_secret_id__in=restricted_ids, user_id=user_id, can_use=True
538
545
  ).values("user_secret_id")
539
- granted = {str(g["user_secret_id"]) for g in grant_rows}
546
+ granted_field_ids = {str(g["user_secret_id"]) for g in field_grants}
547
+ if restricted_item_ids:
548
+ item_grants = await UserSecretGrant.filter(
549
+ credential_item_id__in=restricted_item_ids,
550
+ user_id=user_id,
551
+ can_use=True,
552
+ ).values("credential_item_id")
553
+ granted_item_ids = {str(g["credential_item_id"]) for g in item_grants}
540
554
 
541
555
  resolved: dict[str, str] = {}
542
556
  used_ids: list[str] = []
543
557
  for row in rows:
544
558
  if row.get("access_mode") == "restricted" and not is_org_admin:
545
- if str(row["id"]) not in granted:
559
+ field_granted = str(row["id"]) in granted_field_ids
560
+ item_granted = (
561
+ bool(row.get("credential_item_id"))
562
+ and str(row["credential_item_id"]) in granted_item_ids
563
+ )
564
+ if not field_granted and not item_granted:
546
565
  continue
547
566
  try:
548
567
  resolved[str(row["key"])] = decrypt_value(row["value_encrypted"])
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "matrx-orm"
3
- version = "3.1.80"
3
+ version = "3.1.82"
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" }
@@ -21,7 +21,6 @@ from matrx_orm.core.expressions import (
21
21
  Q,
22
22
  Subquery,
23
23
  TsRank,
24
- _iter_subquery_conditions,
25
24
  )
26
25
  from matrx_orm.query.executor import QueryExecutor, _render_update_expression
27
26
 
@@ -78,22 +77,24 @@ def test_ts_rank_allows_alternate_config():
78
77
  assert "to_tsvector('spanish', body)" in sql
79
78
 
80
79
 
81
- # ── _iter_subquery_conditions (the filters-LIST flattener) ──────────────────
80
+ # ── Inner-query filter rendering (dict / leaf Q / compound Q / negated Q) ───
81
+ # The flattener that raised on compound/negated Q was replaced by
82
+ # _render_subquery_q; see test_subquery_nested_expressions.py for the tree
83
+ # cases. These pin the flat shapes at the SQL-text level.
82
84
 
83
85
 
84
- def test_iter_subquery_conditions_dicts_and_leaf_q():
85
- items = [{"a": 1}, Q(b=2)]
86
- assert list(_iter_subquery_conditions(items)) == [("a", 1), ("b", 2)]
87
-
88
-
89
- def test_iter_subquery_conditions_rejects_compound_q():
90
- with pytest.raises(NotImplementedError, match="simple keyword"):
91
- list(_iter_subquery_conditions([Q(a=1) | Q(b=2)]))
92
-
86
+ def test_subquery_renders_dict_and_leaf_q_filters():
87
+ from matrx_orm.core.registry import ModelRegistry
93
88
 
94
- def test_iter_subquery_conditions_rejects_negated_q():
95
- with pytest.raises(NotImplementedError):
96
- list(_iter_subquery_conditions([~Q(a=1)]))
89
+ ModelRegistry.clear()
90
+ _parent, child = _models()
91
+ params: list = []
92
+ sql = Subquery(child.filter(n=1).filter(Q(site_id="s")).select("id")).as_sql(
93
+ "public.sites", params
94
+ )
95
+ assert "WHERE n = $1 AND site_id = $2" in sql
96
+ assert params == [1, "s"]
97
+ ModelRegistry.clear()
97
98
 
98
99
 
99
100
  # ── End-to-end SQL (pattern B — dummy adapter) ──────────────────────────────
@@ -91,12 +91,10 @@ class TestGetModelByTable:
91
91
  ModelRegistry.register(M)
92
92
  assert ModelRegistry.get_model_by_table("public", "table_test_2") is M
93
93
 
94
- def test_model_without_table_name_not_indexed(self):
94
+ def test_modelmeta_derived_table_name_is_indexed(self):
95
95
  M = _make_model("NoTableName")
96
96
  ModelRegistry.register(M)
97
- # No _table_name set -> nothing to look up by table; must not crash
98
- # and must not accidentally satisfy an unrelated lookup.
99
- assert ModelRegistry.get_model_by_table("public", "") is None
97
+ assert ModelRegistry.get_model_by_table("public", "no_table_name") is M
100
98
 
101
99
  def test_wrong_schema_returns_none(self):
102
100
  M = _make_model("TableTest3", table_name="table_test_3", schema="widgets")
@@ -0,0 +1,167 @@
1
+ """Level 1: a Subquery whose INNER queryset itself carries an expression filter.
2
+
3
+ Grown from a live production failure in aidream's ``conversations`` tool
4
+ ("search my Claude Code conversations"): the search path scopes a message-text
5
+ lookup to the caller's conversations with
6
+ ``Message.filter(conversation_id__in=Subquery(summaries))`` — and ``summaries``
7
+ was itself already filtered with ``id__in=Subquery(coding_sessions)`` for the
8
+ provider filter. The OUTER builder understands an expression value
9
+ (``_build_condition_with_expr``); ``_render_subquery`` did not — it fell
10
+ through to ``_build_condition``, emitting ``id = ANY($n)`` and BINDING THE
11
+ ``Subquery`` OBJECT AS A PARAMETER. asyncpg then failed to encode it, so
12
+ search+provider died while search-alone and list+provider both worked.
13
+
14
+ The same renderer also rejected a compound/negated ``Q`` in an inner queryset
15
+ (``origin_class='unknown'`` renders ``Q(...) | Q(...)``), which is the same
16
+ "the inner query is a real query too" gap.
17
+
18
+ Pure SQL-text assertions — no DB needed.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import pytest
24
+ from matrx_orm.core.base import Model, ModelMeta
25
+ from matrx_orm.core.expressions import Now, OuterRef, Q, Subquery
26
+ from matrx_orm.core.fields import BooleanField, DateTimeField, TextField, UUIDField
27
+ from matrx_orm.core.registry import ModelRegistry
28
+
29
+
30
+ @pytest.fixture(autouse=True)
31
+ def _clean_registry():
32
+ ModelRegistry.clear()
33
+ yield
34
+ ModelRegistry.clear()
35
+
36
+
37
+ def _models():
38
+ Summary = ModelMeta(
39
+ "SummaryM",
40
+ (Model,),
41
+ {
42
+ "id": UUIDField(primary_key=True),
43
+ "user_id": TextField(null=True),
44
+ "origin_class": TextField(null=True),
45
+ "updated_at": DateTimeField(null=True),
46
+ "_database": "test_db",
47
+ "_table_name": "conversation_summary",
48
+ "_db_schema": "chat",
49
+ },
50
+ )
51
+ Session = ModelMeta(
52
+ "SessionM",
53
+ (Model,),
54
+ {
55
+ "id": UUIDField(primary_key=True),
56
+ "conversation_id": UUIDField(null=True),
57
+ "provider": TextField(null=True),
58
+ "created_by": TextField(null=True),
59
+ "_database": "test_db",
60
+ "_table_name": "coding_session",
61
+ "_db_schema": "chat",
62
+ },
63
+ )
64
+ Message = ModelMeta(
65
+ "MessageM",
66
+ (Model,),
67
+ {
68
+ "id": UUIDField(primary_key=True),
69
+ "conversation_id": UUIDField(null=True),
70
+ "is_visible_to_user": BooleanField(default=True),
71
+ "_database": "test_db",
72
+ "_table_name": "message",
73
+ "_db_schema": "chat",
74
+ },
75
+ )
76
+ return Summary, Session, Message
77
+
78
+
79
+ class TestNestedSubqueryValue:
80
+ def test_inner_subquery_renders_as_sql_not_a_bound_parameter(self):
81
+ Summary, Session, Message = _models()
82
+ sessions = Session.filter(created_by="u1", provider="claude_code").select("conversation_id")
83
+ summaries = Summary.filter(user_id="u1").filter(id__in=Subquery(sessions)).select("id")
84
+
85
+ qb = Message.filter(conversation_id__in=Subquery(summaries))
86
+ executor = qb._get_executor()
87
+ params: list = []
88
+ cond, _ = executor._build_condition_with_expr(
89
+ "conversation_id", "in", Subquery(summaries), params
90
+ )
91
+
92
+ # The nested provider filter must be SQL, never a bind parameter.
93
+ assert "id IN (SELECT conversation_id FROM chat.coding_session" in cond
94
+ assert "ANY(" not in cond
95
+ assert all(not isinstance(p, Subquery) for p in params)
96
+ # Every placeholder in the rendered condition must have a real param.
97
+ assert params == ["u1", "u1", "claude_code"]
98
+ assert "$1" in cond and "$2" in cond and "$3" in cond
99
+ assert "$4" not in cond
100
+
101
+ def test_placeholder_numbering_is_contiguous_with_outer_params(self):
102
+ Summary, Session, Message = _models()
103
+ sessions = Session.filter(provider="codex").select("conversation_id")
104
+ summaries = Summary.filter(user_id="u9").filter(id__in=Subquery(sessions)).select("id")
105
+
106
+ qb = Message.filter(conversation_id__in=Subquery(summaries))
107
+ executor = qb._get_executor()
108
+ params: list = ["already-bound"]
109
+ cond, _ = executor._build_condition_with_expr(
110
+ "conversation_id", "in", Subquery(summaries), params
111
+ )
112
+
113
+ assert params == ["already-bound", "u9", "codex"]
114
+ assert "$2" in cond and "$3" in cond
115
+ assert "$1" not in cond
116
+
117
+ def test_compound_q_in_inner_queryset_renders(self):
118
+ Summary, _Session, Message = _models()
119
+ summaries = (
120
+ Summary.filter(user_id="u1")
121
+ .filter(Q(origin_class="unknown") | Q(origin_class__isnull=True))
122
+ .select("id")
123
+ )
124
+ params: list = []
125
+ sql = Subquery(summaries).as_sql("chat.message", params)
126
+
127
+ assert "(origin_class = $2 OR origin_class IS NULL)" in sql
128
+ assert params == ["u1", "unknown"]
129
+
130
+ def test_negated_q_in_inner_queryset_renders(self):
131
+ Summary, _Session, _Message = _models()
132
+ summaries = Summary.filter(user_id="u1").filter(~Q(origin_class="human")).select("id")
133
+ params: list = []
134
+ sql = Subquery(summaries).as_sql("chat.message", params)
135
+
136
+ assert "NOT (origin_class = $2)" in sql
137
+ assert params == ["u1", "human"]
138
+
139
+ def test_outer_ref_still_correlates_through_a_q(self):
140
+ Summary, Session, _Message = _models()
141
+ inner = Session.filter(Q(conversation_id=OuterRef("id"))).select("conversation_id")
142
+ params: list = []
143
+ sql = Subquery(inner).as_sql("chat.conversation_summary", params)
144
+
145
+ assert "conversation_id = chat.conversation_summary.id" in sql
146
+ assert params == []
147
+
148
+ def test_database_function_value_inside_inner_queryset(self):
149
+ Summary, Session, _Message = _models()
150
+ Session_ = ModelMeta(
151
+ "SessionTimeM",
152
+ (Model,),
153
+ {
154
+ "id": UUIDField(primary_key=True),
155
+ "conversation_id": UUIDField(null=True),
156
+ "last_seen_at": DateTimeField(null=True),
157
+ "_database": "test_db",
158
+ "_table_name": "coding_session",
159
+ "_db_schema": "chat",
160
+ },
161
+ )
162
+ inner = Session_.filter(last_seen_at__lt=Now()).select("conversation_id")
163
+ params: list = []
164
+ sql = Subquery(inner).as_sql("chat.conversation_summary", params)
165
+
166
+ assert "last_seen_at < NOW()" in sql
167
+ assert params == []
@@ -459,6 +459,47 @@ async def test_non_sandbox_resolution_still_drops_keyless_rows_silently(monkeypa
459
459
  assert not [r for r in caplog.records if r.levelno >= logging.ERROR]
460
460
 
461
461
 
462
+ @pytest.mark.asyncio
463
+ async def test_restricted_org_resolution_honors_item_level_grants(monkeypatch):
464
+ from matrx_orm.secrets_battery import org_service
465
+
466
+ rows = [
467
+ {
468
+ "id": "field-1",
469
+ "credential_item_id": "item-1",
470
+ "key": "APPLE_PRIVATE_KEY",
471
+ "value_encrypted": b"ciphertext",
472
+ "access_mode": "restricted",
473
+ }
474
+ ]
475
+ secret_filters: list[dict] = []
476
+ grant_filters: list[dict] = []
477
+ monkeypatch.setattr(org_service, "UserSecret", _fake_user_secret(rows, secret_filters))
478
+ monkeypatch.setattr(org_service, "decrypt_value", lambda _ciphertext: "private-key")
479
+
480
+ class _FakeGrant:
481
+ @classmethod
482
+ def filter(cls, **filters):
483
+ grant_filters.append(filters)
484
+ if "credential_item_id__in" in filters:
485
+ return _FakeQB([{"credential_item_id": "item-1"}])
486
+ return _FakeQB([])
487
+
488
+ monkeypatch.setattr(org_service, "UserSecretGrant", _FakeGrant)
489
+
490
+ resolved = await org_service.resolve_org_secrets("org-1", user_id="user-1")
491
+
492
+ assert resolved == {"APPLE_PRIVATE_KEY": "private-key"}
493
+ assert grant_filters == [
494
+ {"user_secret_id__in": ["field-1"], "user_id": "user-1", "can_use": True},
495
+ {
496
+ "credential_item_id__in": ["item-1"],
497
+ "user_id": "user-1",
498
+ "can_use": True,
499
+ },
500
+ ]
501
+
502
+
462
503
  # ── Sealed reveal refusal + liveness filters ──────────────────────────────
463
504
 
464
505
 
File without changes
File without changes