GeneralManager 0.69.2__tar.gz → 0.70.0__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 (297) hide show
  1. {generalmanager-0.69.2/src/GeneralManager.egg-info → generalmanager-0.70.0}/PKG-INFO +1 -1
  2. {generalmanager-0.69.2 → generalmanager-0.70.0}/pyproject.toml +1 -1
  3. {generalmanager-0.69.2 → generalmanager-0.70.0/src/GeneralManager.egg-info}/PKG-INFO +1 -1
  4. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/_types/permission.py +2 -0
  5. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/api/graphql_errors.py +8 -3
  6. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/api/graphql_resolvers.py +48 -21
  7. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/api/graphql_search.py +16 -4
  8. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/permission/base_permission.py +104 -26
  9. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/permission/manager_based_permission.py +290 -97
  10. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/permission/permission_checks.py +140 -35
  11. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/public_api_registry.py +4 -0
  12. {generalmanager-0.69.2 → generalmanager-0.70.0}/LICENSE +0 -0
  13. {generalmanager-0.69.2 → generalmanager-0.70.0}/README.md +0 -0
  14. {generalmanager-0.69.2 → generalmanager-0.70.0}/setup.cfg +0 -0
  15. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/GeneralManager.egg-info/SOURCES.txt +0 -0
  16. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/GeneralManager.egg-info/dependency_links.txt +0 -0
  17. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/GeneralManager.egg-info/requires.txt +0 -0
  18. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/GeneralManager.egg-info/top_level.txt +0 -0
  19. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/__init__.py +0 -0
  20. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/_types/__init__.py +0 -0
  21. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/_types/api.py +0 -0
  22. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/_types/bucket.py +0 -0
  23. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/_types/cache.py +0 -0
  24. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/_types/dataframes.py +0 -0
  25. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/_types/factory.py +0 -0
  26. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/_types/general_manager.py +0 -0
  27. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/_types/interface.py +0 -0
  28. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/_types/manager.py +0 -0
  29. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/_types/measurement.py +0 -0
  30. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/_types/rule.py +0 -0
  31. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/_types/search.py +0 -0
  32. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/_types/utils.py +0 -0
  33. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/api/__init__.py +0 -0
  34. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/api/graphql.py +0 -0
  35. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/api/graphql_as_of.py +0 -0
  36. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/api/graphql_mutations.py +0 -0
  37. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/api/graphql_prefetch.py +0 -0
  38. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/api/graphql_relations.py +0 -0
  39. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/api/graphql_subscription_consumer.py +0 -0
  40. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/api/graphql_subscriptions.py +0 -0
  41. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/api/graphql_view.py +0 -0
  42. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/api/graphql_warmup.py +0 -0
  43. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/api/graphql_warmup_registry.py +0 -0
  44. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/api/graphql_warmup_tasks.py +0 -0
  45. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/api/mutation.py +0 -0
  46. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/api/notification_batching.py +0 -0
  47. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/api/property.py +0 -0
  48. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/api/registry.py +0 -0
  49. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/api/remote_api.py +0 -0
  50. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/api/remote_invalidation.py +0 -0
  51. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/api/remote_invalidation_client.py +0 -0
  52. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/apps.py +0 -0
  53. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/as_of.py +0 -0
  54. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/bootstrap.py +0 -0
  55. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/bucket/__init__.py +0 -0
  56. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/bucket/base_bucket.py +0 -0
  57. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/bucket/calculation_bucket.py +0 -0
  58. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/bucket/database_bucket.py +0 -0
  59. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/bucket/group_bucket.py +0 -0
  60. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/bucket/indexing.py +0 -0
  61. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/bucket/request_bucket.py +0 -0
  62. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/cache/__init__.py +0 -0
  63. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/cache/cache_decorator.py +0 -0
  64. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/cache/cache_tracker.py +0 -0
  65. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/cache/data_change_context.py +0 -0
  66. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/cache/dependency_cache.py +0 -0
  67. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/cache/dependency_index.py +0 -0
  68. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/cache/dependency_matching.py +0 -0
  69. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/cache/dependency_publish.py +0 -0
  70. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/cache/dependency_shards.py +0 -0
  71. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/cache/model_dependency_collector.py +0 -0
  72. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/cache/run_context.py +0 -0
  73. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/cache/run_context_lru.py +0 -0
  74. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/cache/signals.py +0 -0
  75. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/chat/__init__.py +0 -0
  76. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/chat/audit.py +0 -0
  77. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/chat/bootstrap.py +0 -0
  78. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/chat/checks.py +0 -0
  79. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/chat/consumer.py +0 -0
  80. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/chat/errors.py +0 -0
  81. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/chat/evals/__init__.py +0 -0
  82. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/chat/evals/__main__.py +0 -0
  83. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/chat/evals/baseline.py +0 -0
  84. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/chat/evals/datasets/basic_queries.yaml +0 -0
  85. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/chat/evals/datasets/demo_readiness.yaml +0 -0
  86. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/chat/evals/datasets/edge_cases.yaml +0 -0
  87. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/chat/evals/datasets/follow_ups.yaml +0 -0
  88. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/chat/evals/datasets/large_schema.yaml +0 -0
  89. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/chat/evals/datasets/multi_hop.yaml +0 -0
  90. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/chat/evals/diagnostics.py +0 -0
  91. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/chat/evals/fingerprints.py +0 -0
  92. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/chat/evals/fixtures.py +0 -0
  93. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/chat/evals/judges/__init__.py +0 -0
  94. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/chat/evals/judges/answer_quality.py +0 -0
  95. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/chat/evals/judges/contract.py +0 -0
  96. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/chat/evals/judges/result_accuracy.py +0 -0
  97. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/chat/evals/judges/tool_sequence.py +0 -0
  98. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/chat/evals/readiness.py +0 -0
  99. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/chat/evals/runner.py +0 -0
  100. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/chat/evals/traces.py +0 -0
  101. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/chat/grounding.py +0 -0
  102. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/chat/models.py +0 -0
  103. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/chat/providers/__init__.py +0 -0
  104. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/chat/providers/_shared.py +0 -0
  105. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/chat/providers/anthropic.py +0 -0
  106. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/chat/providers/base.py +0 -0
  107. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/chat/providers/google.py +0 -0
  108. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/chat/providers/ollama.py +0 -0
  109. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/chat/providers/openai.py +0 -0
  110. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/chat/rate_limits.py +0 -0
  111. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/chat/routing.py +0 -0
  112. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/chat/schema_index.py +0 -0
  113. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/chat/settings.py +0 -0
  114. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/chat/signals.py +0 -0
  115. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/chat/system_prompt.py +0 -0
  116. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/chat/tool_metadata.py +0 -0
  117. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/chat/tools.py +0 -0
  118. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/chat/views.py +0 -0
  119. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/conf.py +0 -0
  120. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/dataframes/__init__.py +0 -0
  121. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/dataframes/measurements.py +0 -0
  122. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/factory/__init__.py +0 -0
  123. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/factory/auto_factory.py +0 -0
  124. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/factory/factories.py +0 -0
  125. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/factory/factory_methods.py +0 -0
  126. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/__init__.py +0 -0
  127. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/base_interface.py +0 -0
  128. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/bundles/__init__.py +0 -0
  129. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/bundles/calculation.py +0 -0
  130. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/bundles/database.py +0 -0
  131. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/bundles/remote_manager.py +0 -0
  132. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/bundles/request.py +0 -0
  133. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/capabilities/__init__.py +0 -0
  134. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/capabilities/base.py +0 -0
  135. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/capabilities/builtin.py +0 -0
  136. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/capabilities/calculation/__init__.py +0 -0
  137. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/capabilities/calculation/_compat.py +0 -0
  138. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/capabilities/calculation/lifecycle.py +0 -0
  139. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/capabilities/configuration.py +0 -0
  140. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/capabilities/core/observability.py +0 -0
  141. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/capabilities/core/utils.py +0 -0
  142. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/capabilities/exceptions.py +0 -0
  143. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/capabilities/existing_model/__init__.py +0 -0
  144. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/capabilities/existing_model/_compat.py +0 -0
  145. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/capabilities/existing_model/resolution.py +0 -0
  146. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/capabilities/factory.py +0 -0
  147. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/capabilities/orm/__init__.py +0 -0
  148. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/capabilities/orm/_compat.py +0 -0
  149. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/capabilities/orm/history.py +0 -0
  150. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/capabilities/orm/lifecycle.py +0 -0
  151. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/capabilities/orm/mutations.py +0 -0
  152. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/capabilities/orm/support.py +0 -0
  153. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/capabilities/orm_utils/__init__.py +0 -0
  154. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/capabilities/orm_utils/django_manager_utils.py +0 -0
  155. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/capabilities/orm_utils/field_descriptors.py +0 -0
  156. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/capabilities/orm_utils/payload_normalizer.py +0 -0
  157. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/capabilities/read_only/__init__.py +0 -0
  158. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/capabilities/read_only/_compat.py +0 -0
  159. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/capabilities/read_only/_types.py +0 -0
  160. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/capabilities/read_only/lifecycle.py +0 -0
  161. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/capabilities/read_only/management.py +0 -0
  162. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/capabilities/registry.py +0 -0
  163. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/capabilities/remote_manager.py +0 -0
  164. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/capabilities/request/__init__.py +0 -0
  165. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/infrastructure/__init__.py +0 -0
  166. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/infrastructure/startup_hooks.py +0 -0
  167. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/infrastructure/system_checks.py +0 -0
  168. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/interfaces/__init__.py +0 -0
  169. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/interfaces/calculation.py +0 -0
  170. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/interfaces/database.py +0 -0
  171. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/interfaces/existing_model.py +0 -0
  172. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/interfaces/read_only.py +0 -0
  173. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/interfaces/remote_manager.py +0 -0
  174. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/interfaces/request.py +0 -0
  175. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/manifests/__init__.py +0 -0
  176. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/manifests/capability_builder.py +0 -0
  177. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/manifests/capability_manifest.py +0 -0
  178. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/manifests/capability_models.py +0 -0
  179. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/orm_interface.py +0 -0
  180. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/requests.py +0 -0
  181. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/utils/__init__.py +0 -0
  182. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/utils/database_interface_protocols.py +0 -0
  183. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/utils/errors.py +0 -0
  184. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/utils/history.py +0 -0
  185. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/interface/utils/models.py +0 -0
  186. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/logging.py +0 -0
  187. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/management/commands/chat_cleanup.py +0 -0
  188. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/management/commands/cleanup_upload_intents.py +0 -0
  189. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/management/commands/graphql_warmup.py +0 -0
  190. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/management/commands/graphql_warmup_refresh_due.py +0 -0
  191. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/management/commands/search_index.py +0 -0
  192. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/management/commands/search_reconcile.py +0 -0
  193. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/management/commands/seed_manager_landscape.py +0 -0
  194. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/management/commands/shell.py +0 -0
  195. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/management/commands/workflow_drain_outbox.py +0 -0
  196. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/management/commands/workflow_replay_dead_letters.py +0 -0
  197. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/manager/__init__.py +0 -0
  198. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/manager/general_manager.py +0 -0
  199. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/manager/group_manager.py +0 -0
  200. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/manager/input.py +0 -0
  201. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/manager/meta.py +0 -0
  202. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/measurement/__init__.py +0 -0
  203. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/measurement/measurement.py +0 -0
  204. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/measurement/measurement_field.py +0 -0
  205. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/metrics/__init__.py +0 -0
  206. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/metrics/graphql.py +0 -0
  207. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/migrations/0001_initial.py +0 -0
  208. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/migrations/0002_workflow_outbox_scaling_indexes.py +0 -0
  209. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/migrations/0003_workflow_execution_correlation_constraint.py +0 -0
  210. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/migrations/0004_chat_persistence_models.py +0 -0
  211. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/migrations/0004_search_index_state.py +0 -0
  212. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/migrations/0005_merge_chat_search.py +0 -0
  213. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/migrations/0006_chat_pending_confirmation_scoped_ids.py +0 -0
  214. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/migrations/0007_upload_intent.py +0 -0
  215. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/migrations/0008_upload_finalization_versions.py +0 -0
  216. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/migrations/0009_upload_cleanup_state.py +0 -0
  217. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/migrations/0010_chat_pending_confirmation_portable_uniqueness.py +0 -0
  218. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/migrations/0011_search_index_state_dirty_generation.py +0 -0
  219. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/migrations/__init__.py +0 -0
  220. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/models.py +0 -0
  221. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/permission/__init__.py +0 -0
  222. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/permission/audit.py +0 -0
  223. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/permission/graphql_capabilities.py +0 -0
  224. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/permission/mutation_permission.py +0 -0
  225. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/permission/permission_data_manager.py +0 -0
  226. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/permission/utils.py +0 -0
  227. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/py.typed +0 -0
  228. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/rule/__init__.py +0 -0
  229. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/rule/handler.py +0 -0
  230. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/rule/rule.py +0 -0
  231. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/search/__init__.py +0 -0
  232. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/search/async_tasks.py +0 -0
  233. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/search/backend.py +0 -0
  234. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/search/backend_registry.py +0 -0
  235. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/search/backends/__init__.py +0 -0
  236. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/search/backends/dev.py +0 -0
  237. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/search/backends/meilisearch.py +0 -0
  238. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/search/backends/opensearch.py +0 -0
  239. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/search/backends/typesense.py +0 -0
  240. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/search/checks.py +0 -0
  241. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/search/config.py +0 -0
  242. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/search/indexer.py +0 -0
  243. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/search/invalidation.py +0 -0
  244. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/search/m2m_invalidation.py +0 -0
  245. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/search/models.py +0 -0
  246. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/search/reconciliation.py +0 -0
  247. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/search/registry.py +0 -0
  248. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/search/tasks.py +0 -0
  249. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/search/utils.py +0 -0
  250. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/seeding/__init__.py +0 -0
  251. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/seeding/manager_landscape.py +0 -0
  252. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/uploads/__init__.py +0 -0
  253. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/uploads/adapters.py +0 -0
  254. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/uploads/checks.py +0 -0
  255. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/uploads/config.py +0 -0
  256. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/uploads/errors.py +0 -0
  257. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/uploads/finalization.py +0 -0
  258. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/uploads/graphql.py +0 -0
  259. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/uploads/graphql_types.py +0 -0
  260. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/uploads/metrics.py +0 -0
  261. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/uploads/models.py +0 -0
  262. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/uploads/public.py +0 -0
  263. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/uploads/s3.py +0 -0
  264. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/uploads/services.py +0 -0
  265. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/uploads/tokens.py +0 -0
  266. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/uploads/types.py +0 -0
  267. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/uploads/urls.py +0 -0
  268. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/uploads/views.py +0 -0
  269. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/utils/__init__.py +0 -0
  270. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/utils/args_to_kwargs.py +0 -0
  271. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/utils/filter_parser.py +0 -0
  272. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/utils/format_string.py +0 -0
  273. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/utils/json_encoder.py +0 -0
  274. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/utils/make_cache_key.py +0 -0
  275. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/utils/none_to_zero.py +0 -0
  276. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/utils/path_mapping.py +0 -0
  277. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/utils/public_api.py +0 -0
  278. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/utils/testing.py +0 -0
  279. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/utils/type_checks.py +0 -0
  280. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/workflow/__init__.py +0 -0
  281. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/workflow/actions.py +0 -0
  282. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/workflow/backend_registry.py +0 -0
  283. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/workflow/backends/__init__.py +0 -0
  284. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/workflow/backends/celery.py +0 -0
  285. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/workflow/backends/local.py +0 -0
  286. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/workflow/backends/n8n.py +0 -0
  287. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/workflow/config.py +0 -0
  288. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/workflow/engine.py +0 -0
  289. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/workflow/event_registry.py +0 -0
  290. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/workflow/events.py +0 -0
  291. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/workflow/models.py +0 -0
  292. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/workflow/signal_bridge.py +0 -0
  293. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/workflow/tasks.py +0 -0
  294. {generalmanager-0.69.2 → generalmanager-0.70.0}/src/general_manager/workflow/telemetry.py +0 -0
  295. {generalmanager-0.69.2 → generalmanager-0.70.0}/tests/test_settings.py +0 -0
  296. {generalmanager-0.69.2 → generalmanager-0.70.0}/tests/test_urls.py +0 -0
  297. {generalmanager-0.69.2 → generalmanager-0.70.0}/tests/testing_asgi.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GeneralManager
3
- Version: 0.69.2
3
+ Version: 0.70.0
4
4
  Summary: Modular Django-based data management framework with ORM, GraphQL, fine-grained permissions, rule validation, calculations and caching.
5
5
  Author-email: Tim Kleindick <tkleindick@yahoo.de>
6
6
  License: MIT License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "GeneralManager"
7
- version = "0.69.2"
7
+ version = "0.70.0"
8
8
  description = "Modular Django-based data management framework with ORM, GraphQL, fine-grained permissions, rule validation, calculations and caching."
9
9
  readme = "README.md"
10
10
  authors = [{ name = "Tim Kleindick", email = "tkleindick@yahoo.de" }]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GeneralManager
3
- Version: 0.69.2
3
+ Version: 0.70.0
4
4
  Summary: Modular Django-based data management framework with ORM, GraphQL, fine-grained permissions, rule validation, calculations and caching.
5
5
  Author-email: Tim Kleindick <tkleindick@yahoo.de>
6
6
  License: MIT License
@@ -16,6 +16,7 @@ __all__ = [
16
16
  "OverrideManagerPermission",
17
17
  "PermissionAuditEvent",
18
18
  "PermissionDataManager",
19
+ "PermissionFilterDecision",
19
20
  "configure_audit_logger",
20
21
  "configure_audit_logger_from_settings",
21
22
  "mutation_capability",
@@ -44,6 +45,7 @@ from general_manager.permission.manager_based_permission import (
44
45
  )
45
46
  from general_manager.permission.audit import PermissionAuditEvent
46
47
  from general_manager.permission.permission_data_manager import PermissionDataManager
48
+ from general_manager.permission.permission_checks import PermissionFilterDecision
47
49
  from general_manager.permission.audit import configure_audit_logger
48
50
  from general_manager.permission.audit import configure_audit_logger_from_settings
49
51
  from general_manager.permission.graphql_capabilities import mutation_capability
@@ -655,7 +655,7 @@ def get_read_permission_filter(
655
655
 
656
656
  Returns:
657
657
  A ``ReadPermissionPlan`` with ``filters``, ``requires_instance_check``,
658
- and ``instance_check_reasons`` fields. ``ReadPermissionPlan`` is an
658
+ ``instance_check_reasons``, and ``decision`` fields. ``ReadPermissionPlan`` is an
659
659
  internal adapter from ``general_manager.permission.base_permission``, not
660
660
  a stable public import path, but generated resolvers rely on these
661
661
  fields.
@@ -677,7 +677,8 @@ def get_read_permission_filter(
677
677
  ``("no_prefilter_backend",)``.
678
678
 
679
679
  Managers without a permission factory receive an empty filter plan and
680
- do not require instance checks. This is a default-allow read policy for
680
+ do not require instance checks, with an explicit ``"allow_all"``
681
+ decision. This is a default-allow read policy for
681
682
  managers that do not define ``Permission``; no additional GraphQL
682
683
  permission filtering or per-object read authorization is applied by this
683
684
  helper. More generally, ``filters=[]`` with
@@ -715,4 +716,8 @@ def get_read_permission_filter(
715
716
  )
716
717
  if permission_attribute is not None:
717
718
  raise InvalidReadPermissionConfigurationError(generalManagerClass.__name__)
718
- return ReadPermissionPlan(filters=[], requires_instance_check=False)
719
+ return ReadPermissionPlan(
720
+ filters=[],
721
+ requires_instance_check=False,
722
+ decision="allow_all",
723
+ )
@@ -90,12 +90,17 @@ class UnsupportedExcludeNoneRelationFilterError(ValueError):
90
90
 
91
91
  @dataclass(slots=True)
92
92
  class ReadAuthorizationResult(Generic[GeneralManagerT]):
93
- """Aggregate outcome from GraphQL read prefiltering and row authorization."""
93
+ """Aggregate outcome from GraphQL read prefiltering and row authorization.
94
+
95
+ Counts remain ``None`` when authorization can return a bucket lazily without
96
+ inspecting or counting it. They are populated when a row-level gate actually
97
+ scans candidates; deny-all plans use known zero counts.
98
+ """
94
99
 
95
100
  queryset: Bucket[GeneralManagerT]
96
- candidate_count: int
97
- authorized_count: int
98
- denied_count: int
101
+ candidate_count: int | None
102
+ authorized_count: int | None
103
+ denied_count: int | None
99
104
  backend_shape: str
100
105
  requires_instance_check: bool
101
106
  instance_check_reasons: tuple[str, ...]
@@ -266,7 +271,7 @@ def apply_read_authorization(
266
271
  final row gate runs and an aggregate log event is emitted only if the final
267
272
  authorization result still requires instance checks.
268
273
  Unrestricted plans that do not require instance checks return the original
269
- queryset with matching candidate and authorized counts.
274
+ queryset without evaluating it to compute counts.
270
275
  """
271
276
  permission_plan = get_read_permission_filter(general_manager_class, info)
272
277
  backend_shape = get_backend_shape(general_manager_class)
@@ -275,16 +280,40 @@ def apply_read_authorization(
275
280
  backend_shape=backend_shape,
276
281
  )
277
282
 
283
+ if permission_plan.decision == "deny_all":
284
+ return ReadAuthorizationResult(
285
+ queryset=queryset.none(),
286
+ candidate_count=0,
287
+ authorized_count=0,
288
+ denied_count=0,
289
+ backend_shape=backend_shape,
290
+ requires_instance_check=False,
291
+ instance_check_reasons=(),
292
+ )
293
+
294
+ if permission_plan.decision == "allow_all":
295
+ return ReadAuthorizationResult(
296
+ queryset=queryset,
297
+ candidate_count=None,
298
+ authorized_count=None,
299
+ denied_count=None,
300
+ backend_shape=backend_shape,
301
+ requires_instance_check=False,
302
+ instance_check_reasons=(),
303
+ )
304
+
278
305
  filtered_queryset = queryset
279
- if not permission_plan.requires_instance_check and permission_plan.filters == [
280
- {"filter": {}, "exclude": {}}
281
- ]:
282
- candidate_count = len(queryset)
306
+ if (
307
+ not permission_plan.requires_instance_check
308
+ and len(permission_plan.filters) == 1
309
+ and not permission_plan.filters[0].get("filter")
310
+ and not permission_plan.filters[0].get("exclude")
311
+ ):
283
312
  result = ReadAuthorizationResult(
284
313
  queryset=queryset,
285
- candidate_count=candidate_count,
286
- authorized_count=candidate_count,
287
- denied_count=0,
314
+ candidate_count=None,
315
+ authorized_count=None,
316
+ denied_count=None,
288
317
  backend_shape=backend_shape,
289
318
  requires_instance_check=False,
290
319
  instance_check_reasons=instance_check_reasons,
@@ -332,19 +361,18 @@ def filter_queryset_by_read_permission(
332
361
  Apply final row-level read authorization to a bucket.
333
362
 
334
363
  When an instance gate is not required, or the manager has no Permission
335
- class, the original queryset is returned with all candidates authorized.
364
+ class, the original queryset is returned without eagerly computing counts.
336
365
  Otherwise each candidate is checked with ``can_read_instance()``. Authorized
337
366
  rows with an ``identification["id"]`` are rebuilt through an ``id__in``
338
367
  filter; authorized rows without an id are unioned back as concrete manager
339
368
  instances.
340
369
  """
341
370
  if not requires_instance_check:
342
- candidate_count = len(queryset)
343
371
  return ReadAuthorizationResult(
344
372
  queryset=queryset,
345
- candidate_count=candidate_count,
346
- authorized_count=candidate_count,
347
- denied_count=0,
373
+ candidate_count=None,
374
+ authorized_count=None,
375
+ denied_count=None,
348
376
  backend_shape=backend_shape,
349
377
  requires_instance_check=False,
350
378
  instance_check_reasons=instance_check_reasons,
@@ -354,12 +382,11 @@ def filter_queryset_by_read_permission(
354
382
  general_manager_class, "Permission", None
355
383
  )
356
384
  if PermissionClass is None:
357
- candidate_count = len(queryset)
358
385
  return ReadAuthorizationResult(
359
386
  queryset=queryset,
360
- candidate_count=candidate_count,
361
- authorized_count=candidate_count,
362
- denied_count=0,
387
+ candidate_count=None,
388
+ authorized_count=None,
389
+ denied_count=None,
363
390
  backend_shape=backend_shape,
364
391
  requires_instance_check=False,
365
392
  instance_check_reasons=instance_check_reasons,
@@ -382,6 +382,11 @@ def passes_permission_filters(
382
382
  """
383
383
  if permission_plan is None:
384
384
  permission_plan = get_read_permission_filter(instance.__class__, info)
385
+ decision = getattr(permission_plan, "decision", "conditional")
386
+ if decision == "allow_all":
387
+ return True
388
+ if decision == "deny_all":
389
+ return False
385
390
  permission_filters = permission_plan.filters
386
391
  if not permission_filters:
387
392
  return can_read_instance(instance, info)
@@ -1046,14 +1051,21 @@ def register_search_query(
1046
1051
  for manager_class in manager_classes:
1047
1052
  type_label = manager_class.__name__
1048
1053
  permission_plan = get_read_permission_filter(manager_class, info)
1054
+ decision = getattr(permission_plan, "decision", "conditional")
1055
+ if decision == "deny_all":
1056
+ continue
1049
1057
  backend_shape = get_backend_shape(manager_class)
1050
1058
  instance_check_reasons = resolve_instance_check_reasons(
1051
1059
  permission_plan,
1052
1060
  backend_shape=backend_shape,
1053
1061
  )
1054
- filter_groups = merge_permission_filters(
1055
- parsed_filters,
1056
- permission_plan.filters,
1062
+ filter_groups = (
1063
+ parsed_filters or None
1064
+ if decision == "allow_all"
1065
+ else merge_permission_filters(
1066
+ parsed_filters,
1067
+ permission_plan.filters,
1068
+ )
1057
1069
  )
1058
1070
  total_hits_for_manager = 0
1059
1071
  candidate_hits_for_manager = 0
@@ -1127,7 +1139,7 @@ def register_search_query(
1127
1139
  total += total_hits_for_manager
1128
1140
  if not manager_total_is_exact:
1129
1141
  total_is_exact = False
1130
- if permission_plan.requires_instance_check:
1142
+ if decision == "conditional" and permission_plan.requires_instance_check:
1131
1143
  logger.info(
1132
1144
  "graphql read authorization summary",
1133
1145
  context={
@@ -3,7 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from abc import ABC, abstractmethod
6
- from collections.abc import Callable, Mapping
6
+ from collections.abc import Callable, Collection, Mapping
7
7
  from dataclasses import dataclass
8
8
  from typing import TYPE_CHECKING, Literal, TypeAlias, TypedDict, cast
9
9
 
@@ -16,7 +16,10 @@ from general_manager.permission.audit import (
16
16
  audit_logging_enabled,
17
17
  emit_permission_audit_event,
18
18
  )
19
- from general_manager.permission.permission_checks import permission_functions
19
+ from general_manager.permission.permission_checks import (
20
+ PermissionFilterDecision,
21
+ permission_functions,
22
+ )
20
23
  from general_manager.permission.permission_data_manager import PermissionDataManager
21
24
  from general_manager.permission.utils import (
22
25
  PermissionNotFoundError,
@@ -33,9 +36,15 @@ UserLike: TypeAlias = AbstractBaseUser | AnonymousUser
33
36
  ReadPermissionReason: TypeAlias = Literal[
34
37
  "unfilterable_read_rule",
35
38
  "based_on_class_context",
39
+ "compound_exclude_semantics",
36
40
  "filter_key_conflict",
37
41
  "no_prefilter_backend",
38
42
  ]
43
+ ReadPermissionDecision: TypeAlias = Literal[
44
+ "allow_all",
45
+ "deny_all",
46
+ "conditional",
47
+ ]
39
48
 
40
49
 
41
50
  class PermissionConstraint(TypedDict, total=False):
@@ -98,6 +107,9 @@ class ReadPermissionPlan:
98
107
  least one read rule could not be converted into a backend prefilter.
99
108
  ``"based_on_class_context"`` means delegated permission logic required
100
109
  class-level context that cannot be represented as a row prefilter.
110
+ ``"compound_exclude_semantics"`` means multiple ANDed exclusion predicates
111
+ were conservatively merged into one safe prefilter but require row-level
112
+ authorization to preserve sequential exclusion semantics.
101
113
  ``"filter_key_conflict"`` means generated filter keys conflicted while
102
114
  composing permission filters. ``"no_prefilter_backend"`` means the interface
103
115
  backend cannot enforce read prefilters fully. Reason tuples are request
@@ -106,11 +118,19 @@ class ReadPermissionPlan:
106
118
  ``requires_instance_check=False`` should normally use an empty reasons tuple.
107
119
  Non-empty reasons in that state are diagnostic only and do not force an
108
120
  instance check by themselves.
121
+
122
+ ``decision`` describes the complete read authorization outcome.
123
+ ``"allow_all"`` and ``"deny_all"`` are definitive and therefore use no
124
+ prefilters or instance check. ``"conditional"`` means authorization still
125
+ depends on the listed row constraints, an instance check, or both. The
126
+ default remains conditional so legacy constructors retain their safe
127
+ behavior.
109
128
  """
110
129
 
111
130
  filters: list[PermissionConstraint]
112
131
  requires_instance_check: bool = True
113
132
  instance_check_reasons: tuple[ReadPermissionReason, ...] = ()
133
+ decision: ReadPermissionDecision = "conditional"
114
134
 
115
135
 
116
136
  class PermissionCheckError(PermissionError):
@@ -131,6 +151,29 @@ class PermissionCheckError(PermissionError):
131
151
  )
132
152
 
133
153
 
154
+ class _InvalidPermissionFilterResultError(TypeError):
155
+ """Raised when a registered read-filter callback returns an invalid shape."""
156
+
157
+ def __init__(
158
+ self,
159
+ permission: str,
160
+ result: object,
161
+ *,
162
+ part_name: str | None = None,
163
+ unsupported_keys: Collection[object] | None = None,
164
+ ) -> None:
165
+ if unsupported_keys:
166
+ rendered_keys = ", ".join(sorted(repr(key) for key in unsupported_keys))
167
+ detail = f"unsupported constraint keys: {rendered_keys}"
168
+ elif part_name is None:
169
+ detail = f"unsupported {type(result).__name__}"
170
+ else:
171
+ detail = f"a non-mapping '{part_name}' constraint"
172
+ super().__init__(
173
+ f"Permission filter for fragment '{permission}' returned {detail}."
174
+ )
175
+
176
+
134
177
  class BasePermission(ABC):
135
178
  """Abstract base class defining CRUD permission checks for managers."""
136
179
 
@@ -604,25 +647,67 @@ class BasePermission(ABC):
604
647
  PermissionConstraint: A mapping with optional ``"filter"`` and
605
648
  ``"exclude"`` dictionaries whose lookup values are typed as
606
649
  ``object``. Superusers and unfilterable permissions that return
607
- ``None`` both resolve to empty ``filter`` and ``exclude`` mappings.
650
+ ``None`` or a static decision both resolve to empty ``filter`` and
651
+ ``exclude`` mappings.
608
652
 
609
653
  Raises:
610
654
  PermissionNotFoundError: If no permission function matches the leading name in `permission`.
611
655
  """
612
- if self._is_superuser():
656
+ permission_filter = self._get_permission_filter_result(permission)
657
+ if permission_filter is None or isinstance(
658
+ permission_filter, PermissionFilterDecision
659
+ ):
613
660
  return {"filter": {}, "exclude": {}}
661
+ return permission_filter
662
+
663
+ def _get_permission_filter_result(
664
+ self,
665
+ permission: str,
666
+ ) -> PermissionConstraint | PermissionFilterDecision | None:
667
+ """Resolve and normalize one permission-filter callback result.
668
+
669
+ Static decisions and ``None`` are preserved for read-plan composition.
670
+ Mapping results are copied into plain constraint dictionaries. Invalid
671
+ callback results fail here so query planning never accidentally treats
672
+ them as authorization constraints.
673
+ """
674
+ if self._is_superuser():
675
+ return PermissionFilterDecision.ALLOW_ALL
614
676
  permission_function, *config = permission.split(":")
615
677
  if permission_function not in permission_functions:
616
678
  raise PermissionNotFoundError(permission)
617
- permission_filter = cast(
618
- PermissionConstraint | None,
619
- permission_functions[permission_function]["permission_filter"](
620
- self.request_user, config
621
- ),
679
+ result: object = permission_functions[permission_function]["permission_filter"](
680
+ self.request_user,
681
+ config,
622
682
  )
623
- if permission_filter is None:
624
- return {"filter": {}, "exclude": {}}
625
- return permission_filter
683
+ if result is None or isinstance(result, PermissionFilterDecision):
684
+ return result
685
+ if not isinstance(result, Mapping):
686
+ raise _InvalidPermissionFilterResultError(permission, result)
687
+ unsupported_keys = set(result).difference(("filter", "exclude"))
688
+ if unsupported_keys:
689
+ raise _InvalidPermissionFilterResultError(
690
+ permission,
691
+ result,
692
+ unsupported_keys=unsupported_keys,
693
+ )
694
+
695
+ constraint: PermissionConstraint = {}
696
+ for part_name in ("filter", "exclude"):
697
+ if part_name not in result:
698
+ continue
699
+ part = result[part_name]
700
+ if not isinstance(part, Mapping):
701
+ raise _InvalidPermissionFilterResultError(
702
+ permission,
703
+ result,
704
+ part_name=part_name,
705
+ )
706
+ if part_name == "filter":
707
+ constraint["filter"] = dict(part)
708
+ else:
709
+ constraint["exclude"] = dict(part)
710
+ return constraint
626
711
 
627
712
  def _get_permission_filter_info(
628
713
  self, permission: str
@@ -631,8 +716,9 @@ class BasePermission(ABC):
631
716
  Resolve filter/exclude constraints and whether the permission is query-filterable.
632
717
 
633
718
  Superusers return empty filter/exclude mappings marked filterable. When
634
- a permission filter returns ``None``, empty mappings are returned with
635
- ``False`` to signal that callers must keep a row-level instance check.
719
+ a permission filter returns ``None`` or a static decision, empty
720
+ mappings are returned with ``False`` to signal that callers must keep
721
+ a row-level instance check until static decisions are planned directly.
636
722
 
637
723
  Returns:
638
724
  ``(constraint, is_filterable)``.
@@ -641,18 +727,10 @@ class BasePermission(ABC):
641
727
  PermissionNotFoundError: If no registered permission function
642
728
  matches the leading permission name.
643
729
  """
644
- if self._is_superuser():
645
- return {"filter": {}, "exclude": {}}, True
646
- permission_function, *config = permission.split(":")
647
- if permission_function not in permission_functions:
648
- raise PermissionNotFoundError(permission)
649
- permission_filter = cast(
650
- PermissionConstraint | None,
651
- permission_functions[permission_function]["permission_filter"](
652
- self.request_user, config
653
- ),
654
- )
655
- if permission_filter is None:
730
+ permission_filter = self._get_permission_filter_result(permission)
731
+ if permission_filter is None or isinstance(
732
+ permission_filter, PermissionFilterDecision
733
+ ):
656
734
  return {"filter": {}, "exclude": {}}, False
657
735
  return permission_filter, True
658
736