framework-m-core 0.30.2__tar.gz → 0.32.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 (281) hide show
  1. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/CHANGELOG.md +25 -0
  2. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/PKG-INFO +7 -7
  3. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/README.md +3 -3
  4. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/pyproject.toml +3 -3
  5. framework_m_core-0.32.0/src/framework_m_core/constants.py +12 -0
  6. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/container.py +8 -0
  7. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/user.py +17 -15
  8. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/workflow.py +4 -0
  9. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/workflow_state_definition.py +7 -1
  10. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/__init__.py +14 -1
  11. framework_m_core-0.32.0/src/framework_m_core/interfaces/payload_crypto.py +62 -0
  12. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/read_model.py +15 -9
  13. framework_m_core-0.32.0/src/framework_m_core/interfaces/search.py +252 -0
  14. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/workflow.py +3 -0
  15. {framework_m_core-0.30.2/src/framework_m_core/locales/fr → framework_m_core-0.32.0/src/framework_m_core/locales/en}/common.json +3 -0
  16. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/locales/es/common.json +3 -0
  17. {framework_m_core-0.30.2/src/framework_m_core/locales/hi → framework_m_core-0.32.0/src/framework_m_core/locales/fr}/common.json +3 -0
  18. {framework_m_core-0.30.2/src/framework_m_core/locales/en → framework_m_core-0.32.0/src/framework_m_core/locales/hi}/common.json +3 -0
  19. framework_m_core-0.32.0/src/framework_m_core/locales/mr/common.json +335 -0
  20. framework_m_core-0.32.0/src/framework_m_core/locales/ta/common.json +335 -0
  21. framework_m_core-0.32.0/src/framework_m_core/locales/ur/common.json +335 -0
  22. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_child_table_permissions.py +14 -7
  23. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_exports.py +1 -1
  24. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/events/test_auth_events.py +27 -7
  25. framework_m_core-0.32.0/tests/unit/core/interfaces/test_search.py +164 -0
  26. framework_m_core-0.30.2/src/framework_m_core/interfaces/search.py +0 -132
  27. framework_m_core-0.30.2/src/framework_m_core/locales/mr/common.json +0 -332
  28. framework_m_core-0.30.2/src/framework_m_core/locales/ta/common.json +0 -332
  29. framework_m_core-0.30.2/src/framework_m_core/locales/ur/common.json +0 -332
  30. framework_m_core-0.30.2/tests/unit/core/interfaces/test_search.py +0 -73
  31. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/.gitignore +0 -0
  32. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/LICENSE +0 -0
  33. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/__init__.py +0 -0
  34. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/cli/__init__.py +0 -0
  35. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/cli/config.py +0 -0
  36. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/cli/main.py +0 -0
  37. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/cli/plugin_loader.py +0 -0
  38. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/cli/utility.py +0 -0
  39. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/config.py +0 -0
  40. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/decorators.py +0 -0
  41. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/di.py +0 -0
  42. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/__init__.py +0 -0
  43. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/activity_log.py +0 -0
  44. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/api_key.py +0 -0
  45. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/auto_report.py +0 -0
  46. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/custom_permission.py +0 -0
  47. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/document_share.py +0 -0
  48. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/email_queue.py +0 -0
  49. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/error_log.py +0 -0
  50. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/file.py +0 -0
  51. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/idempotency_key.py +0 -0
  52. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/job_log.py +0 -0
  53. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/landing_candidate.py +0 -0
  54. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/letterhead.py +0 -0
  55. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/notification.py +0 -0
  56. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/print_format.py +0 -0
  57. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/recent_document.py +0 -0
  58. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/report.py +0 -0
  59. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/saved_view.py +0 -0
  60. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/scheduled_job.py +0 -0
  61. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/session.py +0 -0
  62. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/social_account.py +0 -0
  63. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/system_settings.py +0 -0
  64. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/tenant_translation.py +0 -0
  65. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/todo.py +0 -0
  66. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/translation.py +0 -0
  67. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/webhook.py +0 -0
  68. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/webhook_log.py +0 -0
  69. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/workflow_state.py +0 -0
  70. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/workflow_transition.py +0 -0
  71. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/workflow_transition_definition.py +0 -0
  72. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/domain/__init__.py +0 -0
  73. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/domain/base_controller.py +0 -0
  74. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/domain/base_doctype.py +0 -0
  75. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/domain/diff.py +0 -0
  76. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/domain/landing.py +0 -0
  77. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/domain/meta.py +0 -0
  78. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/domain/migration_engine.py +0 -0
  79. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/domain/mixins.py +0 -0
  80. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/domain/naming_counter.py +0 -0
  81. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/domain/navigation.py +0 -0
  82. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/domain/outbox.py +0 -0
  83. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/domain/redactor.py +0 -0
  84. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/events/__init__.py +0 -0
  85. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/events/handlers.py +0 -0
  86. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/exceptions.py +0 -0
  87. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/audit.py +0 -0
  88. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/auth_context.py +0 -0
  89. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/authentication.py +0 -0
  90. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/base_doctype.py +0 -0
  91. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/bootstrap.py +0 -0
  92. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/cache.py +0 -0
  93. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/composition.py +0 -0
  94. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/controller.py +0 -0
  95. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/db_resolver.py +0 -0
  96. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/defaults.py +0 -0
  97. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/discovery.py +0 -0
  98. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/email_queue.py +0 -0
  99. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/email_sender.py +0 -0
  100. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/event_bus.py +0 -0
  101. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/feature_entitlement.py +0 -0
  102. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/i18n.py +0 -0
  103. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/idempotency.py +0 -0
  104. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/identity.py +0 -0
  105. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/introspection.py +0 -0
  106. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/job_logger.py +0 -0
  107. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/job_queue.py +0 -0
  108. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/landing.py +0 -0
  109. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/logging.py +0 -0
  110. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/metering.py +0 -0
  111. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/navigation.py +0 -0
  112. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/notification.py +0 -0
  113. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/oauth.py +0 -0
  114. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/permission.py +0 -0
  115. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/print.py +0 -0
  116. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/redactor.py +0 -0
  117. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/registry.py +0 -0
  118. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/report_engine.py +0 -0
  119. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/repository.py +0 -0
  120. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/schema_mapper.py +0 -0
  121. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/secrets.py +0 -0
  122. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/session.py +0 -0
  123. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/sms_gateway.py +0 -0
  124. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/socket.py +0 -0
  125. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/storage.py +0 -0
  126. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/tenant.py +0 -0
  127. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/ticker.py +0 -0
  128. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/webhook.py +0 -0
  129. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/worker.py +0 -0
  130. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/jobs_registry.py +0 -0
  131. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/metadata_decorator.py +0 -0
  132. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/permission_lookup.py +0 -0
  133. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/permissions.py +0 -0
  134. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/pii.py +0 -0
  135. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/py.typed +0 -0
  136. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/registry.py +0 -0
  137. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/rls.py +0 -0
  138. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/rpc_registry.py +0 -0
  139. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/schema_enrichment.py +0 -0
  140. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/security.py +0 -0
  141. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/services/__init__.py +0 -0
  142. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/services/defaults_service.py +0 -0
  143. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/services/document_id.py +0 -0
  144. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/services/health_server.py +0 -0
  145. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/services/landing.py +0 -0
  146. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/services/secrets_service.py +0 -0
  147. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/services/user_manager.py +0 -0
  148. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/services/webhook_dispatcher.py +0 -0
  149. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/system_context.py +0 -0
  150. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/types/job_context.py +0 -0
  151. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/unit_of_work.py +0 -0
  152. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/cli/test_cli_commands.py +0 -0
  153. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/cli/test_cli_init.py +0 -0
  154. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/cli/test_cli_main.py +0 -0
  155. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/cli/test_config.py +0 -0
  156. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/cli/test_info_plugins.py +0 -0
  157. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/cli/test_plugin_loader.py +0 -0
  158. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/cli/test_utility.py +0 -0
  159. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/conftest.py +0 -0
  160. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/doctypes/test_api_key.py +0 -0
  161. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/doctypes/test_job_log.py +0 -0
  162. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/doctypes/test_report.py +0 -0
  163. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/doctypes/test_scheduled_job.py +0 -0
  164. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/doctypes/test_social_account.py +0 -0
  165. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/doctypes/test_user.py +0 -0
  166. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/doctypes/test_webhook.py +0 -0
  167. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/doctypes/test_webhook_log.py +0 -0
  168. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/doctypes/test_workflow_ui.py +0 -0
  169. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/domain/test_meta_discovery.py +0 -0
  170. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/domain/test_naming_counter.py +0 -0
  171. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/events/test_doc_events.py +0 -0
  172. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/interfaces/test_cache.py +0 -0
  173. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/interfaces/test_email_queue_protocol.py +0 -0
  174. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/interfaces/test_idempotency.py +0 -0
  175. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/interfaces/test_identity.py +0 -0
  176. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/interfaces/test_repository_temporal.py +0 -0
  177. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/interfaces/test_sms_gateway.py +0 -0
  178. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/interfaces/test_socket.py +0 -0
  179. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/interfaces/test_workflow.py +0 -0
  180. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/services/test_user_manager.py +0 -0
  181. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/services/test_webhook_dispatcher.py +0 -0
  182. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_activity_log.py +0 -0
  183. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_container.py +0 -0
  184. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_container_async.py +0 -0
  185. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_custom_permission.py +0 -0
  186. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_custom_protocols.py +0 -0
  187. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_di.py +0 -0
  188. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_document_share.py +0 -0
  189. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_email_queue.py +0 -0
  190. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_error_log.py +0 -0
  191. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_exceptions.py +0 -0
  192. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_file.py +0 -0
  193. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_job_decorator.py +0 -0
  194. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_landing_candidate.py +0 -0
  195. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_link_field_leakage.py +0 -0
  196. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_meta_registry_baking.py +0 -0
  197. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_meta_registry_overrides.py +0 -0
  198. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_metadata_decorator.py +0 -0
  199. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_metadata_runtime_patch.py +0 -0
  200. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_notification.py +0 -0
  201. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_object_level_permissions.py +0 -0
  202. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_outbox.py +0 -0
  203. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_permission_config.py +0 -0
  204. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_permission_conveniences.py +0 -0
  205. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_permission_lookup.py +0 -0
  206. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_permissions.py +0 -0
  207. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_pii.py +0 -0
  208. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_print_format.py +0 -0
  209. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_rls.py +0 -0
  210. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_rpc_decorator.py +0 -0
  211. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_schema_extension.py +0 -0
  212. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_security.py +0 -0
  213. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_session.py +0 -0
  214. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_share_lookup.py +0 -0
  215. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_singles.py +0 -0
  216. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_system_context.py +0 -0
  217. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_system_settings.py +0 -0
  218. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_table_alteration.py +0 -0
  219. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_team_based_access.py +0 -0
  220. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_tenant.py +0 -0
  221. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_ui_context_registry.py +0 -0
  222. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_unit_of_work.py +0 -0
  223. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_user_preferences_locale.py +0 -0
  224. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_whitelist_decorator.py +0 -0
  225. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/types/test_job_context.py +0 -0
  226. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/domain/test_landing_models.py +0 -0
  227. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/domain/test_navigation_models.py +0 -0
  228. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/domain/test_registry_roles.py +0 -0
  229. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/interfaces/test_activity_registry.py +0 -0
  230. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/interfaces/test_authentication.py +0 -0
  231. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/interfaces/test_discovery_port.py +0 -0
  232. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/interfaces/test_logging_protocol.py +0 -0
  233. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/interfaces/test_redactor_port.py +0 -0
  234. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/interfaces/test_worker_protocol.py +0 -0
  235. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/services/test_health_server.py +0 -0
  236. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/doctypes/test_auto_report.py +0 -0
  237. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/doctypes/test_saved_view.py +0 -0
  238. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/domain/test_base_controller.py +0 -0
  239. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/domain/test_base_controller_audit.py +0 -0
  240. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/domain/test_base_controller_submitted.py +0 -0
  241. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/domain/test_base_doctype_implements_protocol.py +0 -0
  242. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/domain/test_diff.py +0 -0
  243. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/domain/test_migration_engine.py +0 -0
  244. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/domain/test_mixins.py +0 -0
  245. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/domain/test_redactor_logic.py +0 -0
  246. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/events/test_read_model_projector.py +0 -0
  247. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/interfaces/test_audit.py +0 -0
  248. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/interfaces/test_auth_context.py +0 -0
  249. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/interfaces/test_base_doctype_protocol.py +0 -0
  250. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/interfaces/test_bootstrap_protocol.py +0 -0
  251. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/interfaces/test_controller_protocol.py +0 -0
  252. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/interfaces/test_core_report_engine.py +0 -0
  253. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/interfaces/test_db_resolver.py +0 -0
  254. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/interfaces/test_defaults.py +0 -0
  255. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/interfaces/test_event_bus.py +0 -0
  256. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/interfaces/test_feature_entitlement.py +0 -0
  257. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/interfaces/test_i18n.py +0 -0
  258. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/interfaces/test_introspection.py +0 -0
  259. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/interfaces/test_job_queue.py +0 -0
  260. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/interfaces/test_landing.py +0 -0
  261. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/interfaces/test_metering.py +0 -0
  262. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/interfaces/test_navigation.py +0 -0
  263. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/interfaces/test_permission.py +0 -0
  264. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/interfaces/test_print.py +0 -0
  265. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/interfaces/test_repository.py +0 -0
  266. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/interfaces/test_schema_mapper_protocol.py +0 -0
  267. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/interfaces/test_smoke_interfaces.py +0 -0
  268. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/interfaces/test_storage.py +0 -0
  269. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/interfaces/test_ticker.py +0 -0
  270. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/services/test_defaults_service.py +0 -0
  271. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/services/test_document_id.py +0 -0
  272. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/services/test_landing_service.py +0 -0
  273. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/services/test_secrets_service.py +0 -0
  274. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/test_config_expansion.py +0 -0
  275. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/test_config_schema.py +0 -0
  276. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/test_registry.py +0 -0
  277. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/test_registry_discovery.py +0 -0
  278. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/test_schema_enrichment.py +0 -0
  279. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/test_selective_apps.py +0 -0
  280. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/test_webhooks.py +0 -0
  281. {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/test_base_doctype.py +0 -0
@@ -1,3 +1,28 @@
1
+ ## framework-m-core v0.32.0
2
+
3
+ ### Features
4
+
5
+ - add PayloadCryptoProtocol interface for payload encryption (3a036b6)
6
+ - add global search command palette (360fff6)
7
+
8
+ ### Bug Fixes
9
+
10
+ - unify admin roles into a centralized constant (e557428)
11
+ - enforce user field guard blocks and correct admin roles (184736f)
12
+
13
+ ## framework-m-core v0.31.0
14
+
15
+ ### Features
16
+
17
+ - add bounded count threshold for list totals (db01634)
18
+ - support dynamic workflow state field configuration (1ae69e1)
19
+
20
+ ### Bug Fixes
21
+
22
+ - support dynamic workflow state field configuration (cc88877)
23
+ - update package export assertions (e19c1b1)
24
+ - ensure container protocol defaults and type mappings (41df6cb)
25
+
1
26
  ## framework-m-core v0.30.2
2
27
 
3
28
  ### Bug Fixes
@@ -1,13 +1,13 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: framework-m-core
3
- Version: 0.30.2
3
+ Version: 0.32.0
4
4
  Summary: Core protocols and dependency injection for Framework M
5
5
  Author: Framework M Contributors
6
6
  License: Apache-2.0
7
7
  License-File: LICENSE
8
8
  Requires-Python: >=3.12
9
- Requires-Dist: cyclopts>=4.22.4
10
- Requires-Dist: pydantic-settings>=2.14.2
9
+ Requires-Dist: cyclopts>=4.22.5
10
+ Requires-Dist: pydantic-settings>=2.15.0
11
11
  Requires-Dist: pydantic>=2.13.4
12
12
  Description-Content-Type: text/markdown
13
13
 
@@ -16,7 +16,7 @@ Description-Content-Type: text/markdown
16
16
  [![PyPI version](https://badge.fury.io/py/framework-m-core.svg)](https://badge.fury.io/py/framework-m-core)
17
17
  [![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)
18
18
  [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
19
- [![GitLab Pipeline Status](https://gitlab.com/castlecraft/framework-m/badges/main/pipeline.svg)](https://gitlab.com/castlecraft/framework-m/-/pipelines)
19
+ [![GitLab Pipeline Status](https://gitlab.com/framework-m/framework-m/badges/main/pipeline.svg)](https://gitlab.com/framework-m/framework-m/-/pipelines)
20
20
 
21
21
  Core protocols and foundation for Framework M.
22
22
 
@@ -58,9 +58,9 @@ def my_command():
58
58
 
59
59
  ## Usage
60
60
 
61
- This package is typically used as a dependency of `framework-m` or `framework-m-standard`.
61
+ This package is typically used as a dependency of `framework-m` or `framework-m-standard`.
62
62
  For most applications, install the full `framework-m` package instead.
63
63
 
64
64
  ## License
65
65
 
66
- Apache 2.0 License - see [LICENSE](https://gitlab.com/castlecraft/framework-m/blob/main/LICENSE) for details.
66
+ Apache 2.0 License - see [LICENSE](https://gitlab.com/framework-m/framework-m/blob/main/LICENSE) for details.
@@ -3,7 +3,7 @@
3
3
  [![PyPI version](https://badge.fury.io/py/framework-m-core.svg)](https://badge.fury.io/py/framework-m-core)
4
4
  [![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)
5
5
  [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
6
- [![GitLab Pipeline Status](https://gitlab.com/castlecraft/framework-m/badges/main/pipeline.svg)](https://gitlab.com/castlecraft/framework-m/-/pipelines)
6
+ [![GitLab Pipeline Status](https://gitlab.com/framework-m/framework-m/badges/main/pipeline.svg)](https://gitlab.com/framework-m/framework-m/-/pipelines)
7
7
 
8
8
  Core protocols and foundation for Framework M.
9
9
 
@@ -45,9 +45,9 @@ def my_command():
45
45
 
46
46
  ## Usage
47
47
 
48
- This package is typically used as a dependency of `framework-m` or `framework-m-standard`.
48
+ This package is typically used as a dependency of `framework-m` or `framework-m-standard`.
49
49
  For most applications, install the full `framework-m` package instead.
50
50
 
51
51
  ## License
52
52
 
53
- Apache 2.0 License - see [LICENSE](https://gitlab.com/castlecraft/framework-m/blob/main/LICENSE) for details.
53
+ Apache 2.0 License - see [LICENSE](https://gitlab.com/framework-m/framework-m/blob/main/LICENSE) for details.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "framework-m-core"
3
- version = "0.30.2"
3
+ version = "0.32.0"
4
4
  description = "Core protocols and dependency injection for Framework M"
5
5
  readme = "README.md"
6
6
  license = { text = "Apache-2.0" }
@@ -10,8 +10,8 @@ authors = [
10
10
  ]
11
11
  dependencies = [
12
12
  "pydantic>=2.13.4",
13
- "pydantic-settings>=2.14.2",
14
- "cyclopts>=4.22.4",
13
+ "pydantic-settings>=2.15.0",
14
+ "cyclopts>=4.22.5",
15
15
  ]
16
16
 
17
17
  [build-system]
@@ -0,0 +1,12 @@
1
+ """Core constants used across the framework."""
2
+
3
+ # Canonical set of roles that grant system-wide administrative privileges.
4
+ # These roles bypass Row-Level Security (RLS) and DocType permissions.
5
+ ADMIN_ROLES: frozenset[str] = frozenset(
6
+ {
7
+ "Administrator",
8
+ "SystemManager",
9
+ "System",
10
+ "Admin",
11
+ }
12
+ )
@@ -178,6 +178,10 @@ class Container(DeclarativeContainer):
178
178
  # Usage: container.repository.override(GenericRepository)
179
179
  repository: _Provider[Any] = providers.Dependency()
180
180
 
181
+ # Optional request-scoped database search provider. Standard web wiring
182
+ # composes it with application entry-point providers per request.
183
+ search_provider: _Provider[Any] = providers.Dependency()
184
+
181
185
  # Repository Factory - dynamically instantiates repositories for DocTypes
182
186
  # Usage: container.repository_factory()
183
187
  repository_factory: _Provider[Any] = providers.Dependency()
@@ -259,6 +263,9 @@ class Container(DeclarativeContainer):
259
263
  # Composition Service - handles federated data aggregation
260
264
  composition_service: _Provider[Any] = providers.Dependency()
261
265
 
266
+ # Workflow Service - manages document workflow states and transitions
267
+ workflow_service: _Provider[Any] = providers.Dependency()
268
+
262
269
 
263
270
  def load_overrides(container: Container, group: str = "framework_m.overrides") -> int:
264
271
  """
@@ -698,6 +705,7 @@ def get_app_container_names(main_container: Container) -> list[str]:
698
705
  "migration_manager",
699
706
  "discovery_service",
700
707
  "composition_service",
708
+ "workflow_service",
701
709
  "wiring_config",
702
710
  }
703
711
 
@@ -22,6 +22,7 @@ from typing import Any
22
22
 
23
23
  from pydantic import Field, field_validator, model_validator
24
24
 
25
+ from framework_m_core.constants import ADMIN_ROLES
25
26
  from framework_m_core.domain.base_controller import BaseController
26
27
  from framework_m_core.domain.base_doctype import BaseDocType
27
28
  from framework_m_core.exceptions import PermissionDeniedError
@@ -287,6 +288,7 @@ class LocalUserController(BaseController[LocalUser]):
287
288
 
288
289
  ADMIN_FIELDS: frozenset[str] = frozenset({"roles", "tenants", "is_active"})
289
290
  IDENTITY_FIELDS: frozenset[str] = frozenset({"email", "phone_number"})
291
+ ADMIN_ROLES = ADMIN_ROLES
290
292
 
291
293
  async def before_save(self, context: Any = None) -> None:
292
294
  """Enforce self-service identifier/admin-field protection.
@@ -305,18 +307,16 @@ class LocalUserController(BaseController[LocalUser]):
305
307
  if isinstance(context, dict) and context.get("system_bypass") is True:
306
308
  return
307
309
 
308
- if self._is_admin_context(context):
309
- return
310
-
311
- changed_admin = {
312
- field for field in self.ADMIN_FIELDS if self.has_changed(field)
313
- }
314
- if changed_admin:
315
- raise PermissionDeniedError(
316
- t(
317
- "Self-service users cannot modify administrative fields: {fields}"
318
- ).format(fields=", ".join(sorted(changed_admin)))
319
- )
310
+ if not self._is_admin_context(context):
311
+ changed_admin = {
312
+ field for field in self.ADMIN_FIELDS if self.has_changed(field)
313
+ }
314
+ if changed_admin:
315
+ raise PermissionDeniedError(
316
+ t(
317
+ "Self-service users cannot modify administrative fields: {fields}"
318
+ ).format(fields=", ".join(sorted(changed_admin)))
319
+ )
320
320
 
321
321
  changed_identity = {
322
322
  field for field in self.IDENTITY_FIELDS if self.has_changed(field)
@@ -369,14 +369,16 @@ class LocalUserController(BaseController[LocalUser]):
369
369
 
370
370
  await super().after_save(context)
371
371
 
372
- @staticmethod
373
- def _is_admin_context(context: Any) -> bool:
372
+ @classmethod
373
+ def _is_admin_context(cls, context: Any) -> bool:
374
374
  """Return True when the save is being performed by an admin/system principal."""
375
375
  if isinstance(context, dict):
376
376
  if context.get("is_admin") is True:
377
377
  return True
378
378
  user = context.get("user")
379
- if isinstance(user, UserContext) and "Admin" in (user.roles or []):
379
+ if isinstance(user, UserContext) and any(
380
+ r in cls.ADMIN_ROLES for r in (user.roles or [])
381
+ ):
380
382
  return True
381
383
  return False
382
384
 
@@ -37,6 +37,10 @@ class Workflow(BaseDocType):
37
37
  },
38
38
  },
39
39
  )
40
+ workflow_state_field: str = Field(
41
+ default="workflow_state",
42
+ description="Field on target DocType that stores the workflow state name",
43
+ )
40
44
  initial_state: str = Field(description="Initial state for new documents")
41
45
  states: list[WorkflowStateDefinition] = Field(
42
46
  default_factory=list,
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
  from typing import ClassVar
6
6
 
7
7
  from framework_m_core.domain.base_doctype import BaseDocType, Field
8
+ from framework_m_core.domain.mixins import DocStatus
8
9
 
9
10
 
10
11
  class WorkflowStateDefinition(BaseDocType):
@@ -13,12 +14,17 @@ class WorkflowStateDefinition(BaseDocType):
13
14
  This is a child table for the Workflow DocType.
14
15
 
15
16
  Attributes:
16
- state_name: Name of the state (e.g., Draft, Approved)
17
+ current_state: Name of the state (e.g., Draft, Approved)
18
+ doc_status: Target document status mapping (0: Draft, 1: Submitted, 2: Cancelled)
17
19
  allow_edit: Role allowed to edit documents when in this state
18
20
  is_terminal: Whether this is a final state (no further transitions)
19
21
  """
20
22
 
21
23
  current_state: str = Field(description="Name of the state (e.g., Draft, Approved)")
24
+ doc_status: DocStatus | None = Field(
25
+ default=None,
26
+ description="Target document status mapping (0: Draft, 1: Submitted, 2: Cancelled)",
27
+ )
22
28
  allow_edit: str | None = Field(
23
29
  default=None,
24
30
  description="Role allowed to edit in this state",
@@ -51,6 +51,7 @@ from framework_m_core.interfaces.oauth import (
51
51
  OAuth2Token,
52
52
  OAuth2UserInfo,
53
53
  )
54
+ from framework_m_core.interfaces.payload_crypto import PayloadCryptoProtocol
54
55
  from framework_m_core.interfaces.permission import (
55
56
  DecisionSource,
56
57
  PermissionAction,
@@ -74,7 +75,14 @@ from framework_m_core.interfaces.repository import (
74
75
  RepositoryProtocol,
75
76
  )
76
77
  from framework_m_core.interfaces.schema_mapper import SchemaMapperProtocol
77
- from framework_m_core.interfaces.search import SearchProtocol, SearchResult
78
+ from framework_m_core.interfaces.search import (
79
+ GlobalSearchQuery,
80
+ GlobalSearchResponse,
81
+ SearchCategory,
82
+ SearchProtocol,
83
+ SearchResult,
84
+ SearchSuggestion,
85
+ )
78
86
  from framework_m_core.interfaces.secrets import SecretProtocol
79
87
  from framework_m_core.interfaces.session import SessionData, SessionProtocol
80
88
  from framework_m_core.interfaces.socket import SocketProtocol
@@ -144,6 +152,7 @@ __all__ = [
144
152
  "OrderSpec",
145
153
  "PaginatedResult",
146
154
  "PasswordCredentials",
155
+ "PayloadCryptoProtocol",
147
156
  "PermissionAction",
148
157
  "PermissionProtocol",
149
158
  "PolicyEvaluateRequest",
@@ -158,8 +167,12 @@ __all__ = [
158
167
  "ReportResult",
159
168
  "RepositoryProtocol",
160
169
  "SchemaMapperProtocol",
170
+ "GlobalSearchQuery",
171
+ "GlobalSearchResponse",
172
+ "SearchCategory",
161
173
  "SearchProtocol",
162
174
  "SearchResult",
175
+ "SearchSuggestion",
163
176
  "SecretProtocol",
164
177
  "SendResult",
165
178
  "SessionData",
@@ -0,0 +1,62 @@
1
+ """Payload Encryption Protocol (Port).
2
+
3
+ Defines the interface that payload encryption adapters must implement.
4
+ The framework uses this port to decrypt incoming request bodies and to
5
+ expose public-key material to clients.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Any, Protocol, runtime_checkable
11
+
12
+
13
+ @runtime_checkable
14
+ class PayloadCryptoProtocol(Protocol):
15
+ """Protocol for application-layer payload encryption/decryption.
16
+
17
+ Implementations are responsible for:
18
+ 1. Encrypting plaintext request bodies into an opaque envelope.
19
+ 2. Decrypting incoming envelopes back into plaintext bytes.
20
+ 3. Exposing the server's public key in JWK-compatible JSON so the
21
+ frontend can encrypt payloads without an extra handshake.
22
+
23
+ The middleware layer decides *when* to decrypt based on configuration;
24
+ this provider focuses purely on the cryptographic primitives.
25
+ """
26
+
27
+ def encrypt_payload(self, plaintext: bytes | str) -> bytes:
28
+ """Encrypt a plaintext payload.
29
+
30
+ Args:
31
+ plaintext: The raw request body as bytes or text.
32
+
33
+ Returns:
34
+ The encrypted envelope as bytes. The concrete format is left to
35
+ the adapter (e.g. JWE compact serialization, JSON envelope).
36
+ """
37
+ ...
38
+
39
+ def decrypt_payload(self, payload: bytes | str) -> bytes:
40
+ """Decrypt an encrypted payload.
41
+
42
+ Args:
43
+ payload: The encrypted envelope as bytes or text.
44
+
45
+ Returns:
46
+ The decrypted plaintext as bytes.
47
+
48
+ Raises:
49
+ ValueError: If the payload is malformed or cannot be decrypted.
50
+ """
51
+ ...
52
+
53
+ def get_public_key(self) -> dict[str, Any]:
54
+ """Return the public key in a JWK-compatible dictionary.
55
+
56
+ Returns:
57
+ A JSON-serializable mapping (e.g. ``{"kty": "RSA", "n": "...", ...}``).
58
+ """
59
+ ...
60
+
61
+
62
+ __all__ = ["PayloadCryptoProtocol"]
@@ -18,6 +18,7 @@ from typing import Any, Protocol
18
18
  from pydantic import BaseModel
19
19
 
20
20
  from framework_m_core.interfaces.event_bus import Event
21
+ from framework_m_core.interfaces.repository import FilterSpec
21
22
 
22
23
 
23
24
  class ReadQueryResult(BaseModel):
@@ -59,9 +60,12 @@ class ReadModelProtocol(Protocol):
59
60
  if event.type == "invoice.created":
60
61
  await self._update_monthly_totals(event.data)
61
62
 
62
- async def query(self, filters=None, **kwargs) -> list[dict]:
63
- return await self.clickhouse.execute(
64
- "SELECT month, SUM(total) FROM invoices GROUP BY month"
63
+ async def query(self, filters=None, **kwargs) -> ReadQueryResult:
64
+ return ReadQueryResult(
65
+ items=await self.clickhouse.execute(
66
+ "SELECT month, SUM(total) FROM invoices GROUP BY month"
67
+ ),
68
+ total=12,
65
69
  )
66
70
 
67
71
  async def rebuild(self) -> None:
@@ -87,11 +91,11 @@ class ReadModelProtocol(Protocol):
87
91
 
88
92
  async def query(
89
93
  self,
90
- filters: dict[str, Any] | None = None,
94
+ filters: list[FilterSpec] | None = None,
91
95
  order_by: list[str] | None = None,
92
96
  limit: int = 100,
93
97
  offset: int = 0,
94
- ) -> list[dict[str, Any]]:
98
+ ) -> ReadQueryResult:
95
99
  """Query the read model with optional filtering and pagination.
96
100
 
97
101
  Read model queries are optimized for fast retrieval and
@@ -99,17 +103,19 @@ class ReadModelProtocol(Protocol):
99
103
  (eventual consistency).
100
104
 
101
105
  Args:
102
- filters: Optional key-value filters to apply
106
+ filters: Optional list of filter specs to apply (operators
107
+ preserved, e.g. EQ/LT/LIKE/IN)
103
108
  order_by: List of fields to sort by (prefix with - for desc)
104
109
  limit: Maximum number of results (default: 100)
105
110
  offset: Number of records to skip for pagination
106
111
 
107
112
  Returns:
108
- List of dictionaries representing query results
113
+ ReadQueryResult wrapping the matching items plus a total
114
+ count (for pagination).
109
115
 
110
116
  Example:
111
- results = await read_model.query(
112
- filters={"status": "paid", "year": 2024},
117
+ result = await read_model.query(
118
+ filters=[FilterSpec(field="status", operator=FilterOperator.EQ, value="paid")],
113
119
  order_by=["-total", "customer"],
114
120
  limit=50
115
121
  )
@@ -0,0 +1,252 @@
1
+ """Search Protocol - Core interface for full-text search.
2
+
3
+ This module defines the SearchProtocol for full-text search
4
+ and indexing operations.
5
+
6
+ Supports various backends: Meilisearch, Elasticsearch, Whoosh, etc.
7
+ """
8
+
9
+ from datetime import datetime
10
+ from enum import StrEnum
11
+ from typing import Any, Protocol
12
+
13
+ from pydantic import BaseModel, Field, computed_field, field_validator, model_validator
14
+
15
+
16
+ class SearchCategory(StrEnum):
17
+ """Public categories understood by global-search providers and clients."""
18
+
19
+ DOCUMENTS = "documents"
20
+ DOCTYPES = "doctypes"
21
+ COMMANDS = "commands"
22
+ NAVIGATION = "navigation"
23
+ REPORTS = "reports"
24
+
25
+
26
+ class SearchSuggestion(BaseModel):
27
+ """Normalized item returned by a global-search provider.
28
+
29
+ ``id`` is the globally stable deduplication key. Providers must use the
30
+ same ID for the same logical item across queries (for example,
31
+ ``documents:Customer:CUST-0001``). ``metadata`` is provider metadata for
32
+ backend composition only; the web adapter must explicitly select the
33
+ fields it exposes to a browser.
34
+ """
35
+
36
+ id: str = Field(min_length=1, description="Globally stable result identifier")
37
+ category: SearchCategory
38
+ label: str = Field(min_length=1)
39
+ subtitle: str | None = None
40
+ icon: str | None = None
41
+ url: str | None = None
42
+ score: float | None = None
43
+ created_at: datetime | None = None
44
+ updated_at: datetime | None = None
45
+ highlights: dict[str, list[str]] | None = None
46
+ provider: str | None = Field(default=None, exclude=True)
47
+ metadata: dict[str, Any] = Field(default_factory=dict, exclude=True)
48
+
49
+ @field_validator("id", "label")
50
+ @classmethod
51
+ def _strip_required_text(cls, value: str) -> str:
52
+ """Reject whitespace-only stable IDs and labels."""
53
+ stripped = value.strip()
54
+ if not stripped:
55
+ raise ValueError("must not be blank")
56
+ return stripped
57
+
58
+
59
+ class GlobalSearchQuery(BaseModel):
60
+ """Normalized request options for the ``/search/global`` contract.
61
+
62
+ The route owns the blank-query response (an empty ``suggestions`` list),
63
+ while providers only receive non-empty :attr:`normalized_query` values.
64
+ ``categories`` is intentionally a list so its query-string representation
65
+ has a stable order when clients need shareable URLs.
66
+ """
67
+
68
+ query: str
69
+ typeahead: bool = False
70
+ limit: int = Field(default=20, ge=1, le=100)
71
+ offset: int = Field(default=0, ge=0)
72
+ categories: list[SearchCategory] = Field(default_factory=list)
73
+ created_after: datetime | None = None
74
+ created_before: datetime | None = None
75
+
76
+ @field_validator("categories")
77
+ @classmethod
78
+ def _deduplicate_categories(
79
+ cls, categories: list[SearchCategory]
80
+ ) -> list[SearchCategory]:
81
+ """Preserve the first requested category while removing duplicates."""
82
+ return list(dict.fromkeys(categories))
83
+
84
+ @model_validator(mode="after")
85
+ def _validate_date_range(self) -> "GlobalSearchQuery":
86
+ if (
87
+ self.created_after is not None
88
+ and self.created_before is not None
89
+ and self.created_after > self.created_before
90
+ ):
91
+ raise ValueError("created_after must be before or equal to created_before")
92
+ return self
93
+
94
+ @property
95
+ def normalized_query(self) -> str:
96
+ """Return query text with leading and trailing whitespace removed."""
97
+ return self.query.strip()
98
+
99
+ @property
100
+ def is_blank(self) -> bool:
101
+ """Whether the route must return an empty response without searching."""
102
+ return not self.normalized_query
103
+
104
+
105
+ class GlobalSearchResponse(BaseModel):
106
+ """Public response shape shared by typeahead and full global search."""
107
+
108
+ query: str
109
+ is_typeahead: bool
110
+ suggestions: list[SearchSuggestion] = Field(default_factory=list)
111
+ total: int = Field(default=0, ge=0)
112
+ limit: int = Field(default=20, ge=1)
113
+ offset: int = Field(default=0, ge=0)
114
+ facets: dict[str, dict[str, int]] | None = None
115
+
116
+
117
+ class SearchResult(BaseModel):
118
+ """Result from a search query.
119
+
120
+ Contains matching items with pagination metadata.
121
+
122
+ Attributes:
123
+ items: List of matching documents
124
+ total: Total count of matches
125
+ limit: Maximum results per page
126
+ offset: Number of results skipped
127
+ facets: Optional facet/aggregation results
128
+ highlights: Optional highlighted snippets per doc ID
129
+ """
130
+
131
+ items: list[dict[str, Any]]
132
+ total: int
133
+ limit: int
134
+ offset: int
135
+ facets: dict[str, dict[str, int]] | None = None
136
+ highlights: dict[str, dict[str, str]] | None = None
137
+
138
+ @computed_field # type: ignore[prop-decorator]
139
+ @property
140
+ def has_more(self) -> bool:
141
+ """Check if more results exist beyond current page."""
142
+ return self.offset + len(self.items) < self.total
143
+
144
+
145
+ class SearchProtocol(Protocol):
146
+ """Protocol defining the contract for full-text search.
147
+
148
+ This is the primary port for search operations in the hexagonal
149
+ architecture. Implementations handle different search engines.
150
+
151
+ Global-search convention:
152
+
153
+ * ``doctype="*"`` searches every provider-supported DocType. Providers
154
+ that cannot search a DocType return an empty result rather than raising.
155
+ * Global results use :class:`SearchSuggestion` dictionaries in
156
+ :attr:`SearchResult.items`; legacy document dictionaries remain valid for
157
+ backwards compatibility.
158
+ * A composite provider invokes providers concurrently, preserves configured
159
+ provider order for equal scores, deduplicates by suggestion ``id``, and
160
+ applies ``offset``/``limit`` only after merging and deduplication.
161
+ * ``SearchResult.total`` for a composite is the post-deduplication count
162
+ before paging. Individual provider failures are isolated and observable.
163
+
164
+ Implementations include:
165
+ - MeilisearchAdapter: Meilisearch search engine
166
+ - ElasticsearchAdapter: Elasticsearch/OpenSearch
167
+ - WhooshAdapter: Pure Python search (for development)
168
+
169
+ Example usage:
170
+ search: SearchProtocol = container.get(SearchProtocol)
171
+
172
+ # Index a document
173
+ await search.index("Todo", "todo-001", {"title": "Buy milk"})
174
+
175
+ # Search
176
+ result = await search.search("Todo", "milk", limit=10)
177
+ # Process results
178
+ """
179
+
180
+ async def index(
181
+ self,
182
+ doctype: str,
183
+ doc_id: str,
184
+ doc: dict[str, Any],
185
+ ) -> None:
186
+ """Index a document for searching.
187
+
188
+ Args:
189
+ doctype: DocType name (used as index name)
190
+ doc_id: Document identifier
191
+ doc: Document data to index
192
+ """
193
+ ...
194
+
195
+ async def delete_index(
196
+ self,
197
+ doctype: str,
198
+ doc_id: str,
199
+ ) -> None:
200
+ """Remove a document from the search index.
201
+
202
+ Args:
203
+ doctype: DocType name
204
+ doc_id: Document identifier
205
+ """
206
+ ...
207
+
208
+ async def search(
209
+ self,
210
+ doctype: str,
211
+ query: str,
212
+ *,
213
+ filters: dict[str, Any] | None = None,
214
+ limit: int = 20,
215
+ offset: int = 0,
216
+ ) -> SearchResult:
217
+ """Search for documents.
218
+
219
+ Args:
220
+ doctype: DocType name to search in
221
+ query: Search query string
222
+ filters: Optional field filters
223
+ limit: Maximum results to return
224
+ offset: Number of results to skip
225
+
226
+ Returns:
227
+ SearchResult with matching documents
228
+ """
229
+ ...
230
+
231
+ async def reindex(self, doctype: str) -> int:
232
+ """Rebuild the entire index for a DocType.
233
+
234
+ Fetches all documents from the repository and re-indexes them.
235
+
236
+ Args:
237
+ doctype: DocType name to reindex
238
+
239
+ Returns:
240
+ Number of documents indexed
241
+ """
242
+ ...
243
+
244
+
245
+ __all__ = [
246
+ "GlobalSearchQuery",
247
+ "GlobalSearchResponse",
248
+ "SearchCategory",
249
+ "SearchProtocol",
250
+ "SearchResult",
251
+ "SearchSuggestion",
252
+ ]