GeneralManager 0.86.5__tar.gz → 0.86.7__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 (343) hide show
  1. {generalmanager-0.86.5/src/GeneralManager.egg-info → generalmanager-0.86.7}/PKG-INFO +1 -1
  2. {generalmanager-0.86.5 → generalmanager-0.86.7}/pyproject.toml +1 -1
  3. {generalmanager-0.86.5 → generalmanager-0.86.7/src/GeneralManager.egg-info}/PKG-INFO +1 -1
  4. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/GeneralManager.egg-info/SOURCES.txt +1 -0
  5. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/graphql_mutations.py +59 -9
  6. generalmanager-0.86.7/src/general_manager/api/mutation_controls.py +5 -0
  7. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/remote_api.py +2 -4
  8. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/remote_invalidation.py +210 -103
  9. {generalmanager-0.86.5 → generalmanager-0.86.7}/LICENSE +0 -0
  10. {generalmanager-0.86.5 → generalmanager-0.86.7}/README.md +0 -0
  11. {generalmanager-0.86.5 → generalmanager-0.86.7}/setup.cfg +0 -0
  12. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/GeneralManager.egg-info/dependency_links.txt +0 -0
  13. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/GeneralManager.egg-info/requires.txt +0 -0
  14. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/GeneralManager.egg-info/top_level.txt +0 -0
  15. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/__init__.py +0 -0
  16. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/_types/__init__.py +0 -0
  17. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/_types/api.py +0 -0
  18. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/_types/bucket.py +0 -0
  19. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/_types/cache.py +0 -0
  20. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/_types/dataframes.py +0 -0
  21. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/_types/factory.py +0 -0
  22. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/_types/general_manager.py +0 -0
  23. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/_types/interface.py +0 -0
  24. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/_types/manager.py +0 -0
  25. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/_types/measurement.py +0 -0
  26. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/_types/permission.py +0 -0
  27. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/_types/rule.py +0 -0
  28. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/_types/search.py +0 -0
  29. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/_types/utils.py +0 -0
  30. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/__init__.py +0 -0
  31. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/graphql.py +0 -0
  32. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/graphql_as_of.py +0 -0
  33. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/graphql_errors.py +0 -0
  34. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/graphql_groups.py +0 -0
  35. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/graphql_identifiers.py +0 -0
  36. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/graphql_ordering.py +0 -0
  37. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/graphql_output.py +0 -0
  38. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/graphql_prefetch.py +0 -0
  39. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/graphql_relations.py +0 -0
  40. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/graphql_resolvers.py +0 -0
  41. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/graphql_search.py +0 -0
  42. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/graphql_subscription_consumer.py +0 -0
  43. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/graphql_subscriptions.py +0 -0
  44. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/graphql_type.py +0 -0
  45. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/graphql_view.py +0 -0
  46. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/graphql_warmup.py +0 -0
  47. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/graphql_warmup_registry.py +0 -0
  48. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/graphql_warmup_tasks.py +0 -0
  49. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/mutation.py +0 -0
  50. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/notification_batching.py +0 -0
  51. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/property.py +0 -0
  52. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/registry.py +0 -0
  53. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/remote_invalidation_client.py +0 -0
  54. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/apps.py +0 -0
  55. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/as_of.py +0 -0
  56. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/bootstrap.py +0 -0
  57. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/bucket/__init__.py +0 -0
  58. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/bucket/_materialized_bucket.py +0 -0
  59. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/bucket/_ordering.py +0 -0
  60. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/bucket/base_bucket.py +0 -0
  61. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/bucket/calculation_bucket.py +0 -0
  62. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/bucket/database_bucket.py +0 -0
  63. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/bucket/excel_bucket.py +0 -0
  64. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/bucket/group_bucket.py +0 -0
  65. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/bucket/indexing.py +0 -0
  66. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/bucket/projection.py +0 -0
  67. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/bucket/request_bucket.py +0 -0
  68. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/cache/__init__.py +0 -0
  69. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/cache/_dependency_graph.py +0 -0
  70. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/cache/batch_refresh.py +0 -0
  71. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/cache/cache_decorator.py +0 -0
  72. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/cache/cache_tracker.py +0 -0
  73. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/cache/data_change_context.py +0 -0
  74. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/cache/dependency_cache.py +0 -0
  75. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/cache/dependency_index.py +0 -0
  76. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/cache/dependency_matching.py +0 -0
  77. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/cache/dependency_publish.py +0 -0
  78. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/cache/dependency_shards.py +0 -0
  79. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/cache/model_dependency_collector.py +0 -0
  80. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/cache/run_context.py +0 -0
  81. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/cache/run_context_lru.py +0 -0
  82. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/cache/signals.py +0 -0
  83. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/cache/transaction_barrier.py +0 -0
  84. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/__init__.py +0 -0
  85. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/audit.py +0 -0
  86. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/bootstrap.py +0 -0
  87. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/checks.py +0 -0
  88. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/consumer.py +0 -0
  89. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/context.py +0 -0
  90. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/errors.py +0 -0
  91. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/evals/__init__.py +0 -0
  92. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/evals/__main__.py +0 -0
  93. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/evals/baseline.py +0 -0
  94. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/evals/datasets/basic_queries.yaml +0 -0
  95. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/evals/datasets/demo_readiness.yaml +0 -0
  96. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/evals/datasets/edge_cases.yaml +0 -0
  97. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/evals/datasets/follow_ups.yaml +0 -0
  98. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/evals/datasets/large_schema.yaml +0 -0
  99. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/evals/datasets/multi_hop.yaml +0 -0
  100. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/evals/datasets/planned_orchestration.yaml +0 -0
  101. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/evals/diagnostics.py +0 -0
  102. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/evals/fingerprints.py +0 -0
  103. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/evals/fixtures.py +0 -0
  104. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/evals/judges/__init__.py +0 -0
  105. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/evals/judges/answer_quality.py +0 -0
  106. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/evals/judges/contract.py +0 -0
  107. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/evals/judges/result_accuracy.py +0 -0
  108. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/evals/judges/tool_sequence.py +0 -0
  109. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/evals/readiness.py +0 -0
  110. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/evals/runner.py +0 -0
  111. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/evals/traces.py +0 -0
  112. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/grounding.py +0 -0
  113. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/models.py +0 -0
  114. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/planned/__init__.py +0 -0
  115. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/planned/budget.py +0 -0
  116. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/planned/calculations.py +0 -0
  117. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/planned/catalog.py +0 -0
  118. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/planned/config.py +0 -0
  119. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/planned/events.py +0 -0
  120. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/planned/evidence.py +0 -0
  121. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/planned/models.py +0 -0
  122. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/planned/planner.py +0 -0
  123. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/planned/provider_calls.py +0 -0
  124. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/planned/resolver.py +0 -0
  125. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/planned/routing.py +0 -0
  126. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/planned/scheduler.py +0 -0
  127. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/planned/synthesis.py +0 -0
  128. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/planned/validation.py +0 -0
  129. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/providers/__init__.py +0 -0
  130. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/providers/_shared.py +0 -0
  131. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/providers/anthropic.py +0 -0
  132. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/providers/base.py +0 -0
  133. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/providers/google.py +0 -0
  134. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/providers/ollama.py +0 -0
  135. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/providers/openai.py +0 -0
  136. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/rate_limits.py +0 -0
  137. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/routing.py +0 -0
  138. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/schema_index.py +0 -0
  139. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/settings.py +0 -0
  140. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/signals.py +0 -0
  141. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/system_prompt.py +0 -0
  142. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/tool_metadata.py +0 -0
  143. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/tools.py +0 -0
  144. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/turns.py +0 -0
  145. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/views.py +0 -0
  146. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/conf.py +0 -0
  147. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/dataframes/__init__.py +0 -0
  148. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/dataframes/measurements.py +0 -0
  149. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/factory/__init__.py +0 -0
  150. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/factory/auto_factory.py +0 -0
  151. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/factory/factories.py +0 -0
  152. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/factory/factory_methods.py +0 -0
  153. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/__init__.py +0 -0
  154. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/base_interface.py +0 -0
  155. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/bundles/__init__.py +0 -0
  156. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/bundles/calculation.py +0 -0
  157. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/bundles/database.py +0 -0
  158. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/bundles/excel.py +0 -0
  159. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/bundles/remote_manager.py +0 -0
  160. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/bundles/request.py +0 -0
  161. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/__init__.py +0 -0
  162. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/base.py +0 -0
  163. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/builtin.py +0 -0
  164. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/calculation/__init__.py +0 -0
  165. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/calculation/_compat.py +0 -0
  166. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/calculation/input_resolution.py +0 -0
  167. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/calculation/lifecycle.py +0 -0
  168. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/configuration.py +0 -0
  169. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/core/observability.py +0 -0
  170. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/core/utils.py +0 -0
  171. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/excel/__init__.py +0 -0
  172. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/exceptions.py +0 -0
  173. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/existing_model/__init__.py +0 -0
  174. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/existing_model/_compat.py +0 -0
  175. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/existing_model/resolution.py +0 -0
  176. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/factory.py +0 -0
  177. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/orm/__init__.py +0 -0
  178. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/orm/_compat.py +0 -0
  179. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/orm/bulk.py +0 -0
  180. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/orm/create_many.py +0 -0
  181. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/orm/history.py +0 -0
  182. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/orm/lifecycle.py +0 -0
  183. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/orm/mutations.py +0 -0
  184. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/orm/support.py +0 -0
  185. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/orm_utils/__init__.py +0 -0
  186. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/orm_utils/django_manager_utils.py +0 -0
  187. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/orm_utils/field_descriptors.py +0 -0
  188. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/orm_utils/payload_normalizer.py +0 -0
  189. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/orm_utils/update_state.py +0 -0
  190. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/read_only/__init__.py +0 -0
  191. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/read_only/_compat.py +0 -0
  192. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/read_only/_types.py +0 -0
  193. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/read_only/lifecycle.py +0 -0
  194. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/read_only/management.py +0 -0
  195. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/registry.py +0 -0
  196. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/remote_manager.py +0 -0
  197. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/request/__init__.py +0 -0
  198. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/excel.py +0 -0
  199. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/excel_store.py +0 -0
  200. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/excel_workbook.py +0 -0
  201. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/infrastructure/__init__.py +0 -0
  202. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/infrastructure/startup_hooks.py +0 -0
  203. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/infrastructure/system_checks.py +0 -0
  204. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/interfaces/__init__.py +0 -0
  205. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/interfaces/calculation.py +0 -0
  206. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/interfaces/database.py +0 -0
  207. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/interfaces/excel.py +0 -0
  208. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/interfaces/existing_model.py +0 -0
  209. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/interfaces/read_only.py +0 -0
  210. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/interfaces/remote_manager.py +0 -0
  211. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/interfaces/request.py +0 -0
  212. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/manifests/__init__.py +0 -0
  213. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/manifests/capability_builder.py +0 -0
  214. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/manifests/capability_manifest.py +0 -0
  215. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/manifests/capability_models.py +0 -0
  216. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/orm_interface.py +0 -0
  217. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/requests.py +0 -0
  218. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/utils/__init__.py +0 -0
  219. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/utils/database_interface_protocols.py +0 -0
  220. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/utils/errors.py +0 -0
  221. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/utils/history.py +0 -0
  222. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/utils/models.py +0 -0
  223. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/logging.py +0 -0
  224. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/management/commands/chat_cleanup.py +0 -0
  225. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/management/commands/cleanup_upload_intents.py +0 -0
  226. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/management/commands/graphql_warmup.py +0 -0
  227. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/management/commands/graphql_warmup_refresh_due.py +0 -0
  228. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/management/commands/search_index.py +0 -0
  229. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/management/commands/search_reconcile.py +0 -0
  230. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/management/commands/seed_manager_landscape.py +0 -0
  231. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/management/commands/shell.py +0 -0
  232. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/management/commands/sync_read_only.py +0 -0
  233. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/management/commands/workflow_drain_outbox.py +0 -0
  234. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/management/commands/workflow_replay_dead_letters.py +0 -0
  235. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/manager/__init__.py +0 -0
  236. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/manager/bulk_create.py +0 -0
  237. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/manager/general_manager.py +0 -0
  238. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/manager/group_manager.py +0 -0
  239. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/manager/input.py +0 -0
  240. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/manager/meta.py +0 -0
  241. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/measurement/__init__.py +0 -0
  242. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/measurement/measurement.py +0 -0
  243. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/measurement/measurement_field.py +0 -0
  244. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/metrics/__init__.py +0 -0
  245. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/metrics/graphql.py +0 -0
  246. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/migrations/0001_initial.py +0 -0
  247. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/migrations/0002_workflow_outbox_scaling_indexes.py +0 -0
  248. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/migrations/0003_workflow_execution_correlation_constraint.py +0 -0
  249. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/migrations/0004_chat_persistence_models.py +0 -0
  250. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/migrations/0004_search_index_state.py +0 -0
  251. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/migrations/0005_merge_chat_search.py +0 -0
  252. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/migrations/0006_chat_pending_confirmation_scoped_ids.py +0 -0
  253. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/migrations/0007_upload_intent.py +0 -0
  254. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/migrations/0008_upload_finalization_versions.py +0 -0
  255. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/migrations/0009_upload_cleanup_state.py +0 -0
  256. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/migrations/0010_chat_pending_confirmation_portable_uniqueness.py +0 -0
  257. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/migrations/0011_search_index_state_dirty_generation.py +0 -0
  258. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/migrations/0012_chat_context_watermarks.py +0 -0
  259. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/migrations/__init__.py +0 -0
  260. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/models.py +0 -0
  261. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/permission/__init__.py +0 -0
  262. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/permission/audit.py +0 -0
  263. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/permission/base_permission.py +0 -0
  264. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/permission/graphql_capabilities.py +0 -0
  265. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/permission/manager_based_permission.py +0 -0
  266. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/permission/mutation_permission.py +0 -0
  267. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/permission/permission_checks.py +0 -0
  268. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/permission/permission_data_manager.py +0 -0
  269. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/permission/utils.py +0 -0
  270. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/public_api_registry.py +0 -0
  271. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/py.typed +0 -0
  272. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/rule/__init__.py +0 -0
  273. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/rule/handler.py +0 -0
  274. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/rule/rule.py +0 -0
  275. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/search/__init__.py +0 -0
  276. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/search/async_tasks.py +0 -0
  277. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/search/backend.py +0 -0
  278. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/search/backend_registry.py +0 -0
  279. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/search/backends/__init__.py +0 -0
  280. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/search/backends/dev.py +0 -0
  281. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/search/backends/meilisearch.py +0 -0
  282. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/search/backends/opensearch.py +0 -0
  283. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/search/backends/typesense.py +0 -0
  284. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/search/checks.py +0 -0
  285. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/search/config.py +0 -0
  286. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/search/identity.py +0 -0
  287. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/search/indexer.py +0 -0
  288. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/search/invalidation.py +0 -0
  289. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/search/m2m_invalidation.py +0 -0
  290. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/search/models.py +0 -0
  291. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/search/reconciliation.py +0 -0
  292. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/search/registry.py +0 -0
  293. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/search/tasks.py +0 -0
  294. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/search/utils.py +0 -0
  295. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/seeding/__init__.py +0 -0
  296. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/seeding/manager_landscape.py +0 -0
  297. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/uploads/__init__.py +0 -0
  298. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/uploads/adapters.py +0 -0
  299. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/uploads/checks.py +0 -0
  300. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/uploads/config.py +0 -0
  301. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/uploads/errors.py +0 -0
  302. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/uploads/finalization.py +0 -0
  303. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/uploads/graphql.py +0 -0
  304. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/uploads/graphql_types.py +0 -0
  305. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/uploads/metrics.py +0 -0
  306. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/uploads/models.py +0 -0
  307. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/uploads/public.py +0 -0
  308. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/uploads/s3.py +0 -0
  309. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/uploads/services.py +0 -0
  310. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/uploads/tokens.py +0 -0
  311. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/uploads/types.py +0 -0
  312. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/uploads/urls.py +0 -0
  313. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/uploads/views.py +0 -0
  314. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/utils/__init__.py +0 -0
  315. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/utils/_args_to_kwargs.py +0 -0
  316. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/utils/_cache_key_encoder.py +0 -0
  317. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/utils/_make_cache_key.py +0 -0
  318. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/utils/_none_to_zero.py +0 -0
  319. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/utils/filter_parser.py +0 -0
  320. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/utils/format_string.py +0 -0
  321. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/utils/json_encoder.py +0 -0
  322. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/utils/path_mapping.py +0 -0
  323. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/utils/public_api.py +0 -0
  324. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/utils/testing.py +0 -0
  325. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/utils/type_checks.py +0 -0
  326. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/workflow/__init__.py +0 -0
  327. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/workflow/actions.py +0 -0
  328. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/workflow/backend_registry.py +0 -0
  329. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/workflow/backends/__init__.py +0 -0
  330. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/workflow/backends/celery.py +0 -0
  331. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/workflow/backends/local.py +0 -0
  332. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/workflow/backends/n8n.py +0 -0
  333. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/workflow/config.py +0 -0
  334. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/workflow/engine.py +0 -0
  335. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/workflow/event_registry.py +0 -0
  336. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/workflow/events.py +0 -0
  337. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/workflow/models.py +0 -0
  338. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/workflow/signal_bridge.py +0 -0
  339. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/workflow/tasks.py +0 -0
  340. {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/workflow/telemetry.py +0 -0
  341. {generalmanager-0.86.5 → generalmanager-0.86.7}/tests/test_settings.py +0 -0
  342. {generalmanager-0.86.5 → generalmanager-0.86.7}/tests/test_urls.py +0 -0
  343. {generalmanager-0.86.5 → generalmanager-0.86.7}/tests/testing_asgi.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GeneralManager
3
- Version: 0.86.5
3
+ Version: 0.86.7
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.86.5"
7
+ version = "0.86.7"
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.86.5
3
+ Version: 0.86.7
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
@@ -50,6 +50,7 @@ src/general_manager/api/graphql_warmup.py
50
50
  src/general_manager/api/graphql_warmup_registry.py
51
51
  src/general_manager/api/graphql_warmup_tasks.py
52
52
  src/general_manager/api/mutation.py
53
+ src/general_manager/api/mutation_controls.py
53
54
  src/general_manager/api/notification_batching.py
54
55
  src/general_manager/api/property.py
55
56
  src/general_manager/api/registry.py
@@ -21,6 +21,7 @@ from django.db import models
21
21
  from django.core.exceptions import FieldDoesNotExist
22
22
 
23
23
  from general_manager.interface.base_interface import AttributeTypedDict, InterfaceBase
24
+ from general_manager.api.mutation_controls import MUTATION_CONTROL_KEYS
24
25
  from general_manager.api.graphql_relations import (
25
26
  get_graphql_manager_registry,
26
27
  resolve_general_manager_type,
@@ -55,6 +56,33 @@ class _UnsetHistoryComment:
55
56
  _HISTORY_COMMENT_UNSET = _UnsetHistoryComment()
56
57
 
57
58
 
59
+ class ReservedMutationControlFieldError(ValueError):
60
+ """Raised when an editable field collides with manager execution metadata."""
61
+
62
+ def __init__(self, field_name: str) -> None:
63
+ super().__init__(
64
+ f"Editable GraphQL field {field_name!r} collides with a reserved "
65
+ "manager mutation control. Rename the field or make it read-only."
66
+ )
67
+
68
+
69
+ class ReservedMutationControlInputError(GraphQLError):
70
+ """Report client input attempting to override trusted authorization metadata."""
71
+
72
+ def __init__(self, key: str) -> None:
73
+ super().__init__(
74
+ f"Mutation input cannot include reserved key {key!r}.",
75
+ extensions={"code": "BAD_USER_INPUT"},
76
+ )
77
+
78
+
79
+ def _reject_mutation_authorization_controls(kwargs: MutationPayload) -> None:
80
+ """Allow audit comments but reject client-controlled authorization metadata."""
81
+ for key in sorted(MUTATION_CONTROL_KEYS - {"history_comment"}):
82
+ if key in kwargs:
83
+ raise ReservedMutationControlInputError(key)
84
+
85
+
58
86
  class _EditableGrapheneField(Protocol):
59
87
  """Graphene field instance carrying GeneralManager's dynamic editability flag."""
60
88
 
@@ -168,8 +196,10 @@ def _normalize_mutation_kwargs_for_manager(
168
196
  ORM mutation capabilities expect ``field_id``/``field_id_list`` for manager
169
197
  relations. Structured references pass through their manager constructor so
170
198
  all declared inputs are validated. Scalar reference values stay unchanged.
171
- Unknown keys and already-normalized keys are preserved.
199
+ Unknown keys and already-normalized keys are preserved, except reserved
200
+ authorization controls, which are rejected before and after normalization.
172
201
  """
202
+ _reject_mutation_authorization_controls(kwargs)
173
203
  interface_cls = getattr(general_manager_class, "Interface", None)
174
204
  if interface_cls is None:
175
205
  return dict(kwargs)
@@ -233,6 +263,7 @@ def _normalize_mutation_kwargs_for_manager(
233
263
  )
234
264
  normalized.pop(key, None)
235
265
 
266
+ _reject_mutation_authorization_controls(normalized)
236
267
  return normalized
237
268
 
238
269
 
@@ -351,6 +382,10 @@ def create_write_fields(
351
382
  Mapping from attribute name to a Graphene input field instance. Returned
352
383
  fields carry a dynamic maintainer-facing ``editable`` flag used by the
353
384
  generated mutation class builders.
385
+
386
+ Raises:
387
+ ReservedMutationControlFieldError: An editable field or its normalized
388
+ relation alias collides with reserved manager mutation metadata.
354
389
  """
355
390
  fields: GrapheneFieldMap = {}
356
391
  attribute_types = interface_cls.get_attribute_types()
@@ -360,6 +395,8 @@ def create_write_fields(
360
395
  continue
361
396
  if info["is_derived"]:
362
397
  continue
398
+ if info["is_editable"] and name in MUTATION_CONTROL_KEYS:
399
+ raise ReservedMutationControlFieldError(name)
363
400
  if _is_direct_relation_raw_id_alias(
364
401
  name,
365
402
  attribute_types,
@@ -375,6 +412,13 @@ def create_write_fields(
375
412
  if default is NOT_PROVIDED or callable(default):
376
413
  default = Undefined
377
414
  manager_type = resolve_general_manager_type(typ, manager_registry)
415
+ if (
416
+ info["is_editable"]
417
+ and manager_type is not None
418
+ and not name.endswith(("_id", "_list"))
419
+ and f"{name}_id" in MUTATION_CONTROL_KEYS
420
+ ):
421
+ raise ReservedMutationControlFieldError(name)
378
422
 
379
423
  fld: object
380
424
  if info.get("orm_field_kind") in {"file", "image"}:
@@ -477,6 +521,7 @@ def generate_create_mutation_class(
477
521
  for field_name, value in kwargs.items()
478
522
  if value is not NOT_PROVIDED
479
523
  }
524
+ _reject_mutation_authorization_controls(kwargs)
480
525
  _preflight_mutation_uploads(
481
526
  info=info,
482
527
  general_manager_class=generalManagerClass,
@@ -488,12 +533,14 @@ def generate_create_mutation_class(
488
533
  kwargs = _normalize_mutation_kwargs_for_manager(generalManagerClass, kwargs)
489
534
  history_comment = _pop_history_comment(kwargs)
490
535
  create = cast(_ManagerCreateMethod, generalManagerClass.create)
491
- create_kwargs = {"creator_id": info.context.user.id, **kwargs}
492
536
  if isinstance(history_comment, _UnsetHistoryComment):
493
- instance = create(**create_kwargs)
537
+ instance = create(creator_id=info.context.user.id, **kwargs)
494
538
  else:
495
- create_kwargs["history_comment"] = history_comment
496
- instance = create(**create_kwargs)
539
+ instance = create(
540
+ creator_id=info.context.user.id,
541
+ history_comment=history_comment,
542
+ **kwargs,
543
+ )
497
544
  except GraphQLError:
498
545
  raise
499
546
  except Exception as error:
@@ -587,6 +634,7 @@ def generate_update_mutation_class(
587
634
  for field_name, value in kwargs.items()
588
635
  if value is not NOT_PROVIDED
589
636
  }
637
+ _reject_mutation_authorization_controls(kwargs)
590
638
  _preflight_mutation_uploads(
591
639
  info=info,
592
640
  general_manager_class=generalManagerClass,
@@ -600,12 +648,14 @@ def generate_update_mutation_class(
600
648
  update = cast(
601
649
  _ManagerUpdateMethod, generalManagerClass(**identification).update
602
650
  )
603
- update_kwargs = {"creator_id": info.context.user.id, **kwargs}
604
651
  if isinstance(history_comment, _UnsetHistoryComment):
605
- instance = update(**update_kwargs)
652
+ instance = update(creator_id=info.context.user.id, **kwargs)
606
653
  else:
607
- update_kwargs["history_comment"] = history_comment
608
- instance = update(**update_kwargs)
654
+ instance = update(
655
+ creator_id=info.context.user.id,
656
+ history_comment=history_comment,
657
+ **kwargs,
658
+ )
609
659
  except GraphQLError:
610
660
  raise
611
661
  except Exception as error:
@@ -0,0 +1,5 @@
1
+ """Manager execution metadata reserved across public mutation transports."""
2
+
3
+ MUTATION_CONTROL_KEYS = frozenset(
4
+ {"ignore_permission", "creator_id", "history_comment"}
5
+ )
@@ -16,6 +16,7 @@ from django.http import HttpRequest, HttpResponse, JsonResponse
16
16
  from django.urls import URLPattern, path
17
17
 
18
18
  from general_manager.logging import get_logger
19
+ from general_manager.api.mutation_controls import MUTATION_CONTROL_KEYS
19
20
  from general_manager.bucket._ordering import normalize_ordering
20
21
  from general_manager.bucket.request_bucket import RequestBucket
21
22
 
@@ -25,9 +26,6 @@ if TYPE_CHECKING:
25
26
  logger = get_logger("api.remote")
26
27
 
27
28
  _SEGMENT_RE = re.compile(r"^[a-z0-9]+(?:-[a-z0-9]+)*$")
28
- _MUTATION_CONTROL_KEYS = frozenset(
29
- {"ignore_permission", "creator_id", "history_comment"}
30
- )
31
29
  _MUTATION_METADATA_KEY = "__metadata__"
32
30
  _MUTATION_METADATA_FIELDS = frozenset({"history_comment"})
33
31
  RemoteAPIView = Callable[[HttpRequest], HttpResponse]
@@ -432,7 +430,7 @@ def _parse_json_body(request: HttpRequest) -> RemoteJSONPayload:
432
430
  def _parse_mutation_body(request: HttpRequest) -> tuple[RemoteJSONPayload, str | None]:
433
431
  """Separate schema values from supported remote mutation metadata."""
434
432
  payload = _parse_json_body(request)
435
- for control_key in _MUTATION_CONTROL_KEYS:
433
+ for control_key in MUTATION_CONTROL_KEYS:
436
434
  if control_key in payload:
437
435
  raise RemoteAPIRequestError.reserved_mutation_control(control_key)
438
436
 
@@ -5,18 +5,21 @@ from __future__ import annotations
5
5
  import asyncio
6
6
  from copy import deepcopy
7
7
  from datetime import date, datetime
8
+ from dataclasses import replace
8
9
  import json
9
10
  from importlib import import_module
10
11
  import re
11
12
  from time import perf_counter
12
13
  from uuid import UUID, uuid4
13
14
  from collections.abc import Callable, Coroutine, Mapping, MutableSequence, Sequence
14
- from types import ModuleType
15
+ from types import ModuleType, SimpleNamespace
15
16
  from typing import TYPE_CHECKING, Protocol, TypeAlias, cast
16
17
  from urllib.parse import parse_qs
17
18
 
18
19
  from asgiref.sync import async_to_sync
20
+ from django.contrib.auth import get_user_model
19
21
  from django.conf import settings
22
+ from django.core.handlers.asgi import ASGIHandler
20
23
  from django.db import DEFAULT_DB_ALIAS, transaction
21
24
  from django.urls import re_path
22
25
 
@@ -241,6 +244,85 @@ def emit_remote_invalidation(
241
244
  post_data_change.connect(emit_remote_invalidation, weak=False)
242
245
 
243
246
 
247
+ def _fresh_remote_principal(principal: object) -> object | None:
248
+ """Reload the authenticated account, without reusing cached permission state."""
249
+ if not getattr(principal, "is_authenticated", False):
250
+ return None
251
+ identity = getattr(principal, "pk", None)
252
+ if identity is None:
253
+ return None
254
+ user = get_user_model()._default_manager.filter(pk=identity).first()
255
+ if user is None or not getattr(user, "is_active", True):
256
+ return None
257
+ return user
258
+
259
+
260
+ def _can_receive_remote_event(
261
+ config: RemoteAPIConfig, principal: object, event: Mapping[str, object]
262
+ ) -> bool:
263
+ """Apply the native current-instance read gate; never disclose deleted IDs."""
264
+ if (
265
+ event.get("action") not in {"create", "update"}
266
+ or event.get("protocol_version") != config.protocol_version
267
+ or event.get("base_path") != config.base_path
268
+ or event.get("resource_name") != config.resource_name
269
+ ):
270
+ return False
271
+ identification = event.get("identification")
272
+ if not isinstance(identification, Mapping) or not identification:
273
+ return False
274
+ from general_manager.interface.interfaces.request import RequestInterface
275
+ from general_manager.api.graphql_errors import get_read_permission_filter
276
+ from general_manager.api.graphql_resolvers import (
277
+ prepare_read_authorization,
278
+ finalize_read_authorization,
279
+ )
280
+ from general_manager.bucket._materialized_bucket import MaterializedBucket
281
+ from general_manager.bucket.base_bucket import Bucket
282
+
283
+ try:
284
+ user = _fresh_remote_principal(principal)
285
+ if user is None:
286
+ return False
287
+ instance = config.manager_cls(**dict(identification))
288
+ if isinstance(instance._interface, RequestInterface):
289
+ # Request constructors are lazy. Even a user-only read rule must
290
+ # not authorize an ID whose current detail cannot be loaded.
291
+ instance._interface.set_request_payload_cache(None)
292
+ instance._interface.get_data()
293
+ info = SimpleNamespace(context=SimpleNamespace(user=user))
294
+ plan = get_read_permission_filter(config.manager_cls, info)
295
+ source: Bucket[GeneralManager] = MaterializedBucket(
296
+ config.manager_cls, [instance]
297
+ )
298
+ if plan.decision == "conditional" and any(
299
+ constraint.get("filter") or constraint.get("exclude")
300
+ for constraint in plan.filters
301
+ ):
302
+ # Keep native ORM filter/exclude semantics for this one candidate.
303
+ source = config.manager_cls.all().with_instances([instance])
304
+ preparation = prepare_read_authorization(
305
+ source,
306
+ config.manager_cls,
307
+ info,
308
+ permission_plan=replace(plan, requires_instance_check=False),
309
+ )
310
+ authorized = finalize_read_authorization(
311
+ preparation, config.manager_cls, info, source="remote_invalidation"
312
+ ).queryset
313
+ # Run the native instance gate unconditionally, even for allow-all plans.
314
+ return any(
315
+ item.Permission(item, user).can_read_instance() for item in authorized
316
+ )
317
+ except MemoryError:
318
+ raise
319
+ except Exception: # noqa: BLE001
320
+ # Missing instances and failed authorization lookups have the same
321
+ # externally silent result. Never send a fallback action or identity.
322
+ logger.warning("remote invalidation authorization failed")
323
+ return False
324
+
325
+
244
326
  class RemoteInvalidationConsumer:
245
327
  """Minimal ASGI websocket consumer for remote invalidation events."""
246
328
 
@@ -269,28 +351,15 @@ class RemoteInvalidationConsumer:
269
351
  receive: AsgiReceive,
270
352
  send: AsgiSend,
271
353
  ) -> None:
354
+ """Forward identified changes only under fresh native instance read rights.
355
+
356
+ Admission requires an active authenticated Django account (4401).
357
+ Unknown resources close with 4404, incompatible versions with 4406.
358
+ Deleted, unavailable and identity-free batch events are suppressed.
359
+ Existing client data is not evicted when access is lost.
272
360
  """
273
- Accept a remote invalidation websocket and forward channel-layer events.
274
-
275
- The consumer closes with 4404 for unknown or disabled resources, 4406
276
- for protocol-version mismatches, and 1011 when no channel layer is
277
- available. It accepts matching connections, subscribes to the resource
278
- group, sends JSON invalidation payloads, and discards the channel in a
279
- `finally` block. The generated route supplies `scope["url_route"]
280
- ["kwargs"]["base_path"]` and `["resource_name"]`; the optional
281
- `version` query string parameter is compared with the resource protocol
282
- version. A missing version is accepted, multiple values use the first
283
- parsed value, and invalid UTF-8 query strings propagate decoding errors.
284
- Non-disconnect inbound websocket messages are ignored while the consumer
285
- keeps listening.
286
-
287
- Outbound JSON messages include `protocol_version`, `base_path`,
288
- `resource_name`, `action`, `identification`, and `event_id` in a
289
- `websocket.send` frame with a JSON `text` payload. Extra channel-layer
290
- event fields are ignored; missing required event fields raise `KeyError`.
291
- Client disconnect exits the loop and cleans up without raising. Missing
292
- route kwargs, channel-layer, receive, send, and JSON errors propagate.
293
- """
361
+ from channels.db import database_sync_to_async
362
+
294
363
  from general_manager.manager.meta import GeneralManagerMeta
295
364
 
296
365
  url_route = scope.get("url_route", {})
@@ -314,6 +383,12 @@ class RemoteInvalidationConsumer:
314
383
  if protocol_version and protocol_version != config.protocol_version:
315
384
  await send({"type": "websocket.close", "code": 4406})
316
385
  return
386
+ principal = await database_sync_to_async(_fresh_remote_principal)(
387
+ scope.get("user")
388
+ )
389
+ if principal is None:
390
+ await send({"type": "websocket.close", "code": 4401})
391
+ return
317
392
  channel_layer = _get_channel_layer_safe()
318
393
  if channel_layer is None:
319
394
  await send({"type": "websocket.close", "code": 1011})
@@ -321,13 +396,13 @@ class RemoteInvalidationConsumer:
321
396
  self._channel_layer = channel_layer
322
397
  self._group_name = remote_invalidation_group_name(config)
323
398
  self._channel_name = await channel_layer.new_channel()
324
- await channel_layer.group_add(self._group_name, self._channel_name)
325
- await send({"type": "websocket.accept"})
326
- receive_task: asyncio.Task[AsgiMessage] = asyncio.create_task(receive())
327
- event_task: asyncio.Task[object] = asyncio.create_task(
328
- channel_layer.receive(self._channel_name)
329
- )
399
+ receive_task: asyncio.Task[AsgiMessage] | None = None
400
+ event_task: asyncio.Task[object] | None = None
330
401
  try:
402
+ await channel_layer.group_add(self._group_name, self._channel_name)
403
+ await send({"type": "websocket.accept"})
404
+ receive_task = asyncio.create_task(receive())
405
+ event_task = asyncio.create_task(channel_layer.receive(self._channel_name))
331
406
  while True:
332
407
  done, _ = await asyncio.wait(
333
408
  {receive_task, event_task},
@@ -347,6 +422,9 @@ class RemoteInvalidationConsumer:
347
422
  not disconnect
348
423
  and isinstance(event, Mapping)
349
424
  and event.get("type") == "gm.remote.invalidation"
425
+ and await database_sync_to_async(_can_receive_remote_event)(
426
+ config, principal, event
427
+ )
350
428
  ):
351
429
  await send(
352
430
  {
@@ -372,9 +450,12 @@ class RemoteInvalidationConsumer:
372
450
  await asyncio.gather(event_task, return_exceptions=True)
373
451
  break
374
452
  finally:
375
- receive_task.cancel()
376
- event_task.cancel()
377
- await asyncio.gather(receive_task, event_task, return_exceptions=True)
453
+ pending_tasks = [
454
+ task for task in (receive_task, event_task) if task is not None
455
+ ]
456
+ for task in pending_tasks:
457
+ task.cancel()
458
+ await asyncio.gather(*pending_tasks, return_exceptions=True)
378
459
  if self._group_name is not None and self._channel_name is not None:
379
460
  await channel_layer.group_discard(self._group_name, self._channel_name)
380
461
 
@@ -396,11 +477,19 @@ def ensure_remote_invalidation_route(
396
477
  trailing slash. The helper is idempotent for the same `(base_path,
397
478
  resource_name)` pair using the resolved config values without additional
398
479
  normalization; changed config for an existing key is preserved unchanged.
399
- Routes append after existing websocket patterns. Missing or non-mutable
480
+ Existing middleware wrappers and the live URLRouter are preserved; opaque
481
+ websocket applications are left unchanged with a warning. Routes append
482
+ after existing websocket patterns. Missing or non-mutable
400
483
  `websocket_urlpatterns` are replaced with a new list. The helper supports
401
484
  ASGI modules that expose either a Channels `application_mapping` router or a
402
- plain HTTP application attribute named by `ASGI_APPLICATION`.
485
+ unwrapped Django ASGIHandler attribute named by `ASGI_APPLICATION`. Unknown
486
+ root callables (including closures and custom HTTP wrappers) are left unchanged
487
+ because treating them as HTTP-only could bypass their websocket security.
403
488
  """
489
+ registry = build_remote_api_registry(manager_classes)
490
+ enabled = [config for config in registry.values() if config.websocket_invalidation]
491
+ if not enabled:
492
+ return
404
493
  asgi_path = getattr(settings, "ASGI_APPLICATION", None)
405
494
  if not asgi_path:
406
495
  return
@@ -419,26 +508,34 @@ def ensure_remote_invalidation_route(
419
508
  if "populate() isn't reentrant" in str(exc):
420
509
  return
421
510
  raise
422
- websocket_patterns = _websocket_patterns(asgi_module)
423
- if websocket_patterns is None:
424
- websocket_patterns = []
425
- cast(_AsgiModuleRoutes, asgi_module).websocket_urlpatterns = websocket_patterns
426
511
  application = getattr(asgi_module, attr_name, None)
427
512
  if application is None:
428
513
  return
429
- for route in _websocket_application_routes(application):
430
- if route not in websocket_patterns:
431
- websocket_patterns.append(route)
432
- registry = build_remote_api_registry(manager_classes)
433
- for config in registry.values():
434
- if not config.websocket_invalidation:
435
- continue
514
+ mapped_application = _protocol_application(application)
515
+ websocket_app = (
516
+ mapped_application.application_mapping.get("websocket")
517
+ if mapped_application is not None
518
+ else None
519
+ )
520
+ router = _websocket_router(websocket_app)
521
+ if (websocket_app is not None and router is None) or (
522
+ mapped_application is None and type(application) is not ASGIHandler
523
+ ):
524
+ logger.warning("remote invalidation routes require an accessible URLRouter")
525
+ return
526
+ websocket_patterns = _websocket_patterns(asgi_module)
527
+ patterns = list(websocket_patterns or [])
528
+ if router is not None:
529
+ for route in router.routes:
530
+ if route not in patterns:
531
+ patterns.append(route)
532
+ for config in enabled:
436
533
  normalized = f"{config.base_path.strip('/')}/ws/{config.resource_name}"
437
534
  pattern = rf"^{re.escape(normalized)}/?$"
438
535
  route_exists = any(
439
536
  getattr(route, "_general_manager_remote_ws_key", None)
440
537
  == (config.base_path, config.resource_name)
441
- for route in websocket_patterns
538
+ for route in patterns
442
539
  )
443
540
  if route_exists:
444
541
  continue
@@ -459,11 +556,16 @@ def ensure_remote_invalidation_route(
459
556
  config.base_path,
460
557
  config.resource_name,
461
558
  )
462
- websocket_patterns.append(websocket_route)
463
- if hasattr(application, "application_mapping"):
464
- mapped_application = cast(_ApplicationMapping, application)
559
+ patterns.append(websocket_route)
560
+ if websocket_patterns is None:
561
+ cast(_AsgiModuleRoutes, asgi_module).websocket_urlpatterns = patterns
562
+ else:
563
+ websocket_patterns[:] = patterns
564
+ if router is not None:
565
+ router.routes = list(patterns)
566
+ elif mapped_application is not None:
465
567
  mapped_application.application_mapping["websocket"] = AuthMiddlewareStack(
466
- URLRouter(list(websocket_patterns))
568
+ URLRouter(list(patterns))
467
569
  )
468
570
  else:
469
571
  setattr(
@@ -472,9 +574,7 @@ def ensure_remote_invalidation_route(
472
574
  ProtocolTypeRouter(
473
575
  {
474
576
  "http": application,
475
- "websocket": AuthMiddlewareStack(
476
- URLRouter(list(websocket_patterns))
477
- ),
577
+ "websocket": AuthMiddlewareStack(URLRouter(list(patterns))),
478
578
  }
479
579
  ),
480
580
  )
@@ -491,8 +591,8 @@ def clear_remote_invalidation_routes() -> None:
491
591
  routes in the configured ASGI module are removed regardless of path or
492
592
  resource shape while preserving the order of remaining routes. Returns
493
593
  without changes when ASGI settings, Channels, the ASGI module, or mutable
494
- sequence websocket patterns are unavailable. Router reconstruction errors
495
- propagate.
594
+ sequence websocket patterns are unavailable. The live URLRouter is updated in place through existing middleware wrappers.
595
+ A plain HTTP application is never wrapped merely to clear routes.
496
596
  """
497
597
  asgi_path = getattr(settings, "ASGI_APPLICATION", None)
498
598
  if not asgi_path:
@@ -501,48 +601,31 @@ def clear_remote_invalidation_routes() -> None:
501
601
  module_path, attr_name = asgi_path.rsplit(".", 1)
502
602
  except ValueError:
503
603
  return
504
- try:
505
- from channels.auth import AuthMiddlewareStack
506
- from channels.routing import ProtocolTypeRouter, URLRouter
507
- except ImportError: # pragma: no cover - optional dependency
508
- return
509
604
  try:
510
605
  asgi_module = import_module(module_path)
511
606
  except ImportError:
512
607
  return
513
608
  websocket_patterns = _websocket_patterns(asgi_module)
514
- if websocket_patterns is None:
515
- return
516
- filtered_patterns = [
517
- route
518
- for route in websocket_patterns
519
- if not (
609
+ application = getattr(asgi_module, attr_name, None)
610
+ mapped_application = _protocol_application(application)
611
+ router = _websocket_router(
612
+ mapped_application.application_mapping.get("websocket")
613
+ if mapped_application is not None
614
+ else None
615
+ )
616
+
617
+ def retained(route: object) -> bool:
618
+ return not (
520
619
  getattr(route, "_general_manager_remote_ws", False)
521
620
  or getattr(route, "_general_manager_remote_ws_key", None) is not None
522
621
  )
523
- ]
524
- cast(_AsgiModuleRoutes, asgi_module).websocket_urlpatterns = filtered_patterns
525
- application = getattr(asgi_module, attr_name, None)
526
- if application is None:
527
- return
528
- if hasattr(application, "application_mapping"):
529
- mapped_application = cast(_ApplicationMapping, application)
530
- mapped_application.application_mapping["websocket"] = AuthMiddlewareStack(
531
- URLRouter(list(filtered_patterns))
532
- )
533
- else:
534
- setattr(
535
- asgi_module,
536
- attr_name,
537
- ProtocolTypeRouter(
538
- {
539
- "http": application,
540
- "websocket": AuthMiddlewareStack(
541
- URLRouter(list(filtered_patterns))
542
- ),
543
- }
544
- ),
545
- )
622
+
623
+ if websocket_patterns is not None:
624
+ websocket_patterns[:] = [
625
+ route for route in websocket_patterns if retained(route)
626
+ ]
627
+ if router is not None:
628
+ router.routes = [route for route in router.routes if retained(route)]
546
629
 
547
630
 
548
631
  def _websocket_patterns(
@@ -557,19 +640,43 @@ def _websocket_patterns(
557
640
  return value
558
641
 
559
642
 
560
- def _websocket_application_routes(application: object) -> list[object]:
561
- """Return routes already installed in a Channels websocket router."""
562
- if not hasattr(application, "application_mapping"):
563
- return []
564
- websocket_app = cast(_ApplicationMapping, application).application_mapping.get(
565
- "websocket"
566
- )
567
- router = websocket_app
643
+ class _RouteCollection(Protocol):
644
+ routes: Sequence[object]
645
+
646
+
647
+ def _protocol_application(application: object) -> _ApplicationMapping | None:
648
+ """Find the protocol router without discarding wrappers around it."""
649
+ seen: set[int] = set()
650
+ while application is not None and id(application) not in seen:
651
+ seen.add(id(application))
652
+ if isinstance(getattr(application, "application_mapping", None), dict):
653
+ return cast(_ApplicationMapping, application)
654
+ application = _middleware_inner(application)
655
+ return None
656
+
657
+
658
+ def _websocket_router(application: object) -> _RouteCollection | None:
659
+ """Locate an actual URLRouter through the existing middleware chain."""
660
+ try:
661
+ from channels.routing import URLRouter
662
+ except ImportError: # pragma: no cover - optional dependency
663
+ return None
664
+
568
665
  seen: set[int] = set()
569
- while router is not None and id(router) not in seen:
570
- seen.add(id(router))
571
- routes = getattr(router, "routes", None)
572
- if isinstance(routes, Sequence) and not isinstance(routes, str | bytes):
573
- return list(routes)
574
- router = getattr(router, "inner", None)
575
- return []
666
+ while application is not None and id(application) not in seen:
667
+ seen.add(id(application))
668
+ if isinstance(application, URLRouter):
669
+ return cast(_RouteCollection, application)
670
+ application = _middleware_inner(application)
671
+ return None
672
+
673
+
674
+ def _middleware_inner(application: object) -> object:
675
+ """Traverse standard inner links and Channels' known origin wrapper."""
676
+ try:
677
+ from channels.security.websocket import OriginValidator
678
+ except ImportError: # pragma: no cover - optional dependency
679
+ return getattr(application, "inner", None)
680
+ if isinstance(application, OriginValidator):
681
+ return application.application
682
+ return getattr(application, "inner", None)
File without changes