digitalkin 1.0.0.dev20__tar.gz → 1.0.0.dev22__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 (237) hide show
  1. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/PKG-INFO +2 -2
  2. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/pyproject.toml +2 -2
  3. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/__version__.py +1 -1
  4. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/community/agno/hitl.py +1 -1
  5. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/core/task_manager/module_runner.py +4 -0
  6. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/grpc_servers/exceptions.py +4 -0
  7. digitalkin-1.0.0.dev22/src/digitalkin/grpc_servers/interceptors/permission.py +103 -0
  8. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/grpc_servers/module_servicer.py +40 -1
  9. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/grpc_servers/utils/grpc_client_wrapper.py +2 -1
  10. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/grpc_servers/utils/grpc_error_handler.py +4 -1
  11. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/grpc_servers/stream_error_codes.py +1 -0
  12. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/module/module_context.py +6 -0
  13. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/modules/_base_module.py +35 -0
  14. digitalkin-1.0.0.dev22/src/digitalkin/services/secret/__init__.py +13 -0
  15. digitalkin-1.0.0.dev22/src/digitalkin/services/secret/default_secret.py +45 -0
  16. digitalkin-1.0.0.dev22/src/digitalkin/services/secret/exceptions.py +5 -0
  17. digitalkin-1.0.0.dev22/src/digitalkin/services/secret/grpc_secret.py +63 -0
  18. digitalkin-1.0.0.dev22/src/digitalkin/services/secret/secret_strategy.py +21 -0
  19. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/services_config.py +8 -0
  20. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/services_models.py +2 -0
  21. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/setup/grpc_setup.py +4 -1
  22. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/storage/grpc_storage.py +41 -1
  23. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/user_profile/default_user_profile.py +12 -0
  24. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/user_profile/grpc_user_profile.py +22 -1
  25. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/user_profile/user_profile_strategy.py +15 -0
  26. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin.egg-info/PKG-INFO +2 -2
  27. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin.egg-info/SOURCES.txt +6 -0
  28. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin.egg-info/requires.txt +1 -1
  29. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/LICENSE +0 -0
  30. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/README.md +0 -0
  31. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/examples/base_server/__init__.py +0 -0
  32. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/examples/base_server/mock/__init__.py +0 -0
  33. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/examples/base_server/mock/mock_pb2.py +0 -0
  34. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/examples/base_server/mock/mock_pb2_grpc.py +0 -0
  35. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/examples/base_server/server_async_insecure.py +0 -0
  36. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/examples/base_server/server_async_secure.py +0 -0
  37. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/examples/base_server/server_sync_insecure.py +0 -0
  38. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/examples/base_server/server_sync_secure.py +0 -0
  39. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/examples/bench_module/__init__.py +0 -0
  40. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/examples/bench_module/echo_module.py +0 -0
  41. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/examples/bench_module/models/__init__.py +0 -0
  42. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/examples/bench_module/models/input.py +0 -0
  43. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/examples/bench_module/models/output.py +0 -0
  44. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/examples/bench_module/models/secret.py +0 -0
  45. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/examples/bench_module/models/setup.py +0 -0
  46. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/examples/bench_module/server.py +0 -0
  47. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/examples/bench_module/triggers/__init__.py +0 -0
  48. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/examples/bench_module/triggers/message_trigger.py +0 -0
  49. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/examples/modules/__init__.py +0 -0
  50. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/examples/modules/archetype_with_tools_module.py +0 -0
  51. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/examples/modules/cpu_intensive_module.py +0 -0
  52. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/examples/modules/dynamic_setup_module.py +0 -0
  53. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/examples/modules/minimal_llm_module.py +0 -0
  54. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/examples/modules/text_transform_module.py +0 -0
  55. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/examples/monitoring/digitalkin_observability/__init__.py +0 -0
  56. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/examples/monitoring/digitalkin_observability/http_server.py +0 -0
  57. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/examples/monitoring/digitalkin_observability/interceptors.py +0 -0
  58. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/examples/monitoring/digitalkin_observability/metrics.py +0 -0
  59. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/examples/monitoring/digitalkin_observability/prometheus.py +0 -0
  60. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/examples/monitoring/tests/test_metrics.py +0 -0
  61. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/examples/redis_demo/client.py +0 -0
  62. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/examples/redis_demo/echo_module.py +0 -0
  63. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/examples/redis_demo/models/__init__.py +0 -0
  64. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/examples/redis_demo/models/input.py +0 -0
  65. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/examples/redis_demo/models/output.py +0 -0
  66. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/examples/redis_demo/models/secret.py +0 -0
  67. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/examples/redis_demo/models/setup.py +0 -0
  68. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/examples/redis_demo/server.py +0 -0
  69. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/examples/redis_demo/triggers/__init__.py +0 -0
  70. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/examples/redis_demo/triggers/message_trigger.py +0 -0
  71. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/examples/services/filesystem_module.py +0 -0
  72. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/examples/services/storage_module.py +0 -0
  73. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/setup.cfg +0 -0
  74. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/__init__.py +0 -0
  75. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/community/__init__.py +0 -0
  76. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/community/agno/__init__.py +0 -0
  77. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/community/agno/agno_adapter.py +0 -0
  78. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/community/agno/agui_tools.py +0 -0
  79. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/community/agno/models.py +0 -0
  80. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/core/__init__.py +0 -0
  81. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/core/common/__init__.py +0 -0
  82. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/core/common/factories.py +0 -0
  83. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/core/exceptions.py +0 -0
  84. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/core/job_manager/__init__.py +0 -0
  85. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/core/job_manager/base_job_manager.py +0 -0
  86. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/core/job_manager/single_job_manager.py +0 -0
  87. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/core/profiling/__init__.py +0 -0
  88. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/core/profiling/step_timer.py +0 -0
  89. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/core/profiling/task_profiler.py +0 -0
  90. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/core/resilience/__init__.py +0 -0
  91. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/core/resilience/bulkhead.py +0 -0
  92. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/core/resilience/task_supervisor.py +0 -0
  93. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/core/task_manager/__init__.py +0 -0
  94. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/core/task_manager/base_task_manager.py +0 -0
  95. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/core/task_manager/local_task_manager.py +0 -0
  96. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/core/task_manager/redis/__init__.py +0 -0
  97. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/core/task_manager/redis/proto_streams.py +0 -0
  98. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/core/task_manager/redis/redis_client.py +0 -0
  99. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/core/task_manager/redis/redis_idempotency.py +0 -0
  100. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/core/task_manager/redis/redis_signal.py +0 -0
  101. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/core/task_manager/redis/redis_state.py +0 -0
  102. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/core/task_manager/remote_task_manager.py +0 -0
  103. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/core/task_manager/task_executor.py +0 -0
  104. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/core/task_manager/task_session.py +0 -0
  105. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/exceptions.py +0 -0
  106. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/grpc_servers/__init__.py +0 -0
  107. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/grpc_servers/_base_server.py +0 -0
  108. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/grpc_servers/gateway_servicer.py +0 -0
  109. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/grpc_servers/interceptors/__init__.py +0 -0
  110. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/grpc_servers/interceptors/request_ids.py +0 -0
  111. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/grpc_servers/m2m_call_registry.py +0 -0
  112. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/grpc_servers/module_server.py +0 -0
  113. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/grpc_servers/stream_registry.py +0 -0
  114. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/grpc_servers/stream_session.py +0 -0
  115. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/grpc_servers/utils/__init__.py +0 -0
  116. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/grpc_servers/utils/circuit_breaker.py +0 -0
  117. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/grpc_servers/utils/utility_schema_extender.py +0 -0
  118. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/grpc_servers/utils/validators.py +0 -0
  119. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/logger.py +0 -0
  120. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/mixins/__init__.py +0 -0
  121. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/mixins/agui_mixin.py +0 -0
  122. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/mixins/base_mixin.py +0 -0
  123. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/mixins/cost_mixin.py +0 -0
  124. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/mixins/file_history_mixin.py +0 -0
  125. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/mixins/filesystem_mixin.py +0 -0
  126. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/mixins/logger_mixin.py +0 -0
  127. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/mixins/storage_mixin.py +0 -0
  128. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/__init__.py +0 -0
  129. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/core/__init__.py +0 -0
  130. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/core/job_manager_models.py +0 -0
  131. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/core/redis.py +0 -0
  132. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/core/task_monitor.py +0 -0
  133. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/events/__init__.py +0 -0
  134. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/events/agent_events.py +0 -0
  135. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/grpc_servers/__init__.py +0 -0
  136. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/grpc_servers/circuit_breaker.py +0 -0
  137. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/grpc_servers/m2m.py +0 -0
  138. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/grpc_servers/models.py +0 -0
  139. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/grpc_servers/types.py +0 -0
  140. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/module/__init__.py +0 -0
  141. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/module/ag_ui.py +0 -0
  142. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/module/base_types.py +0 -0
  143. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/module/module.py +0 -0
  144. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/module/module_types.py +0 -0
  145. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/module/request_metadata.py +0 -0
  146. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/module/select_schema.py +0 -0
  147. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/module/setup_types.py +0 -0
  148. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/module/tool_cache.py +0 -0
  149. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/module/tool_reference.py +0 -0
  150. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/module/utility.py +0 -0
  151. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/services/__init__.py +0 -0
  152. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/services/cost.py +0 -0
  153. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/services/registry.py +0 -0
  154. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/services/services.py +0 -0
  155. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/services/storage.py +0 -0
  156. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/settings/__init__.py +0 -0
  157. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/settings/gateway.py +0 -0
  158. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/settings/grpc_client.py +0 -0
  159. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/settings/log.py +0 -0
  160. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/settings/module.py +0 -0
  161. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/settings/profiling.py +0 -0
  162. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/settings/queue.py +0 -0
  163. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/settings/redis.py +0 -0
  164. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/settings/resilience.py +0 -0
  165. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/settings/server/__init__.py +0 -0
  166. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/settings/server/channel.py +0 -0
  167. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/settings/server/grpc.py +0 -0
  168. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/settings/server/server.py +0 -0
  169. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/settings/server/servicer.py +0 -0
  170. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/settings/task_manager.py +0 -0
  171. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/settings/utils/__init__.py +0 -0
  172. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/settings/utils/channel.py +0 -0
  173. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/utils/__init__.py +0 -0
  174. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/models/utils/dynamic_schema.py +0 -0
  175. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/modules/__init__.py +0 -0
  176. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/modules/archetype_module.py +0 -0
  177. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/modules/tool_module.py +0 -0
  178. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/modules/trigger_handler.py +0 -0
  179. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/modules/triggers/__init__.py +0 -0
  180. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/modules/triggers/healthcheck_ping_trigger.py +0 -0
  181. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/modules/triggers/healthcheck_services_trigger.py +0 -0
  182. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/modules/triggers/healthcheck_status_trigger.py +0 -0
  183. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/py.typed +0 -0
  184. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/__init__.py +0 -0
  185. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/base_strategy.py +0 -0
  186. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/communication/__init__.py +0 -0
  187. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/communication/communication_strategy.py +0 -0
  188. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/communication/default_communication.py +0 -0
  189. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/communication/exceptions.py +0 -0
  190. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/communication/grpc_communication.py +0 -0
  191. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/cost/__init__.py +0 -0
  192. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/cost/cost_strategy.py +0 -0
  193. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/cost/default_cost.py +0 -0
  194. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/cost/exceptions.py +0 -0
  195. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/cost/grpc_cost.py +0 -0
  196. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/filesystem/__init__.py +0 -0
  197. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/filesystem/default_filesystem.py +0 -0
  198. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/filesystem/exceptions.py +0 -0
  199. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/filesystem/filesystem_strategy.py +0 -0
  200. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/filesystem/grpc_filesystem.py +0 -0
  201. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/identity/__init__.py +0 -0
  202. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/identity/default_identity.py +0 -0
  203. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/identity/identity_strategy.py +0 -0
  204. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/registry/__init__.py +0 -0
  205. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/registry/default_registry.py +0 -0
  206. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/registry/exceptions.py +0 -0
  207. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/registry/grpc_registry.py +0 -0
  208. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/registry/registry_models.py +0 -0
  209. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/registry/registry_strategy.py +0 -0
  210. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/setup/__init__.py +0 -0
  211. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/setup/default_setup.py +0 -0
  212. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/setup/exceptions.py +0 -0
  213. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/setup/setup_strategy.py +0 -0
  214. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/storage/__init__.py +0 -0
  215. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/storage/default_storage.py +0 -0
  216. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/storage/exceptions.py +0 -0
  217. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/storage/storage_strategy.py +0 -0
  218. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/task_manager/__init__.py +0 -0
  219. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/task_manager/default_task_manager.py +0 -0
  220. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/task_manager/exceptions.py +0 -0
  221. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/task_manager/redis_task_manager.py +0 -0
  222. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/task_manager/task_manager_strategy.py +0 -0
  223. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/user_profile/__init__.py +0 -0
  224. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/services/user_profile/exceptions.py +0 -0
  225. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/utils/__init__.py +0 -0
  226. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/utils/arg_parser.py +0 -0
  227. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/utils/conditional_schema.py +0 -0
  228. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/utils/development_mode_action.py +0 -0
  229. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/utils/dynamic_schema.py +0 -0
  230. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/utils/exceptions.py +0 -0
  231. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/utils/llm_ready_schema.py +0 -0
  232. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/utils/package_discover.py +0 -0
  233. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/utils/proto_utils.py +0 -0
  234. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin/utils/schema_splitter.py +0 -0
  235. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin.egg-info/dependency_links.txt +0 -0
  236. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/src/digitalkin.egg-info/top_level.txt +0 -0
  237. {digitalkin-1.0.0.dev20 → digitalkin-1.0.0.dev22}/tests/test_exceptions.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: digitalkin
3
- Version: 1.0.0.dev20
3
+ Version: 1.0.0.dev22
4
4
  Summary: SDK to build kin used in DigitalKin
5
5
  Author-email: "DigitalKin.ai" <contact@digitalkin.ai>
6
6
  License: Attribution-NonCommercial-ShareAlike 4.0 International
@@ -453,7 +453,7 @@ Requires-Python: >=3.10
453
453
  Description-Content-Type: text/markdown
454
454
  License-File: LICENSE
455
455
  Requires-Dist: ag-ui-protocol>=0.1.18
456
- Requires-Dist: agentic-mesh-protocol==1.0.0a0
456
+ Requires-Dist: agentic-mesh-protocol==1.0.0a1
457
457
  Requires-Dist: anyio>=4.13.0
458
458
  Requires-Dist: grpcio-health-checking==1.81.0
459
459
  Requires-Dist: grpcio-reflection==1.81.0
@@ -28,7 +28,7 @@
28
28
 
29
29
  dependencies = [
30
30
  "ag-ui-protocol>=0.1.18",
31
- "agentic-mesh-protocol==1.0.0a0",
31
+ "agentic-mesh-protocol==1.0.0a1",
32
32
  "anyio>=4.13.0",
33
33
  "grpcio-health-checking==1.81.0",
34
34
  "grpcio-reflection==1.81.0",
@@ -37,7 +37,7 @@
37
37
  "pydantic-settings>=2.14.1",
38
38
  "redis[hiredis]>=7.4.0,<9",
39
39
  ]
40
- version = "1.0.0.dev20"
40
+ version = "1.0.0.dev22"
41
41
 
42
42
  [project.optional-dependencies]
43
43
  performance = [ "uvloop>=0.21" ]
@@ -5,4 +5,4 @@ from importlib.metadata import PackageNotFoundError, version
5
5
  try:
6
6
  __version__ = version("digitalkin")
7
7
  except PackageNotFoundError:
8
- __version__ = "1.0.0.dev20"
8
+ __version__ = "1.0.0.dev22"
@@ -611,7 +611,7 @@ class AgnoHitlRunner:
611
611
  stream: Any,
612
612
  send: Callable[[BaseAgentRunEvent], Coroutine[Any, Any, None]],
613
613
  thread_id: str,
614
- run_output_cls: type,
614
+ run_output_cls: type[RunOutput],
615
615
  ) -> PauseInfo | None:
616
616
  """Drain an Agno stream, forward events, persist on pause.
617
617
 
@@ -13,6 +13,7 @@ from redis.exceptions import RedisError
13
13
  from digitalkin.core.exceptions import BackpressureTimeoutError
14
14
  from digitalkin.core.profiling.step_timer import StepTimer
15
15
  from digitalkin.core.profiling.task_profiler import TaskProfiler
16
+ from digitalkin.grpc_servers.exceptions import PermissionDeniedError
16
17
  from digitalkin.grpc_servers.interceptors.request_ids import RequestContext
17
18
  from digitalkin.logger import logger
18
19
  from digitalkin.models.grpc_servers.stream_error_codes import StreamErrorCode
@@ -226,6 +227,9 @@ class ModuleRunner:
226
227
  StreamErrorCode.REDIS_UNAVAILABLE.value,
227
228
  f"redis unavailable: {type(exc).__name__}: {exc}",
228
229
  )
230
+ except PermissionDeniedError as exc:
231
+ logger.warning("ModuleRunner: setup access denied: %s", exc, extra=log_extra)
232
+ await on_fatal(StreamErrorCode.SETUP_ACCESS_DENIED.value, str(exc))
229
233
  except Exception as exc:
230
234
  logger.exception("ModuleRunner: module job failed", extra=log_extra)
231
235
  await on_fatal(
@@ -19,6 +19,10 @@ class SecurityError(ServerError):
19
19
  """Error related to security configuration."""
20
20
 
21
21
 
22
+ class PermissionDeniedError(ServerError):
23
+ """Remote service rejected the call with gRPC PERMISSION_DENIED."""
24
+
25
+
22
26
  class ServerStateError(ServerError):
23
27
  """Error related to server state (e.g., already started, not started)."""
24
28
 
@@ -0,0 +1,103 @@
1
+ """Client-side permission middleware for gRPC service-data access.
2
+
3
+ A single cross-cutting interceptor: any unary call a backend rejects with
4
+ ``PERMISSION_DENIED`` surfaces as :class:`PermissionDeniedError`, so callers
5
+ never handle permission per service. Attached on every channel, it covers all
6
+ data services and module-to-module calls uniformly.
7
+
8
+ The interceptor *returns* a terminal denied call rather than raising: raising a
9
+ non-``AioRpcError`` from an aio interceptor leaks into the intercepted call's
10
+ ``__del__`` (grpc only swallows ``AioRpcError``/``CancelledError`` there), so we
11
+ mirror grpc's own ``UnaryUnaryCallResponse`` pattern and raise from ``__await__``.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from typing import TYPE_CHECKING, Any
17
+
18
+ import grpc
19
+ import grpc.aio
20
+
21
+ from digitalkin.grpc_servers.exceptions import PermissionDeniedError
22
+ from digitalkin.logger import logger
23
+
24
+ if TYPE_CHECKING:
25
+ from collections.abc import Awaitable, Callable
26
+
27
+
28
+ class _DeniedUnaryUnaryCall(grpc.aio.UnaryUnaryCall):
29
+ """A finished unary call that raises ``PermissionDeniedError`` when awaited."""
30
+
31
+ def __init__(self, method: str, details: str) -> None:
32
+ self._message = f"[{method}] {details or 'permission denied'}"
33
+ self._details = details
34
+
35
+ def cancel(self) -> bool: # noqa: PLR6301
36
+ return False
37
+
38
+ def cancelled(self) -> bool: # noqa: PLR6301
39
+ return False
40
+
41
+ def done(self) -> bool: # noqa: PLR6301
42
+ return True
43
+
44
+ def add_done_callback(self, unused_callback: Any) -> None:
45
+ """No-op: the call is already finished.
46
+
47
+ Args:
48
+ unused_callback: Ignored; present to satisfy the call interface.
49
+ """
50
+
51
+ def time_remaining(self) -> float | None: # noqa: PLR6301
52
+ return None
53
+
54
+ async def initial_metadata(self) -> grpc.aio.Metadata: # noqa: PLR6301
55
+ return grpc.aio.Metadata()
56
+
57
+ async def trailing_metadata(self) -> grpc.aio.Metadata: # noqa: PLR6301
58
+ return grpc.aio.Metadata()
59
+
60
+ async def code(self) -> grpc.StatusCode: # noqa: PLR6301
61
+ return grpc.StatusCode.PERMISSION_DENIED
62
+
63
+ async def details(self) -> str:
64
+ return self._details
65
+
66
+ async def debug_error_string(self) -> None: # noqa: PLR6301
67
+ return None
68
+
69
+ async def wait_for_connection(self) -> None:
70
+ """No-op: the call already resolved to a permission denial."""
71
+
72
+ def __await__(self) -> Any:
73
+ raise PermissionDeniedError(self._message)
74
+ yield # unreachable; marks this method a generator so it is awaitable
75
+
76
+
77
+ class PermissionClientInterceptor(grpc.aio.UnaryUnaryClientInterceptor):
78
+ """Map a ``PERMISSION_DENIED`` unary reply to ``PermissionDeniedError``."""
79
+
80
+ async def intercept_unary_unary( # noqa: PLR6301
81
+ self,
82
+ continuation: Callable[[grpc.aio.ClientCallDetails, Any], Awaitable[Any]],
83
+ client_call_details: grpc.aio.ClientCallDetails,
84
+ request: Any,
85
+ ) -> Any:
86
+ """Return a denied call on PERMISSION_DENIED, else pass the real call through.
87
+
88
+ Args:
89
+ continuation: Downstream call continuation.
90
+ client_call_details: Original call details.
91
+ request: Request message.
92
+
93
+ Returns:
94
+ The downstream call, or a terminal call raising PermissionDeniedError on await.
95
+ """
96
+ call = await continuation(client_call_details, request)
97
+ if await call.code() == grpc.StatusCode.PERMISSION_DENIED:
98
+ method = client_call_details.method
99
+ method_name = method.decode() if isinstance(method, bytes) else method
100
+ details = await call.details()
101
+ logger.warning("permission denied on %s: %s", method_name, details or "")
102
+ return _DeniedUnaryUnaryCall(method_name, details)
103
+ return call
@@ -14,12 +14,13 @@ from agentic_mesh_protocol.module.v1 import (
14
14
  lifecycle_pb2,
15
15
  module_service_pb2_grpc,
16
16
  )
17
+ from agentic_mesh_protocol.user_profile.v1 import user_profile_pb2
17
18
  from google.protobuf import json_format, struct_pb2
18
19
 
19
20
  from digitalkin.core.job_manager.base_job_manager import BaseJobManager
20
21
  from digitalkin.core.job_manager.single_job_manager import SingleJobManager
21
22
  from digitalkin.core.task_manager.redis.redis_signal import SharedRedisListener
22
- from digitalkin.grpc_servers.exceptions import ServicerError
23
+ from digitalkin.grpc_servers.exceptions import PermissionDeniedError, ServicerError
23
24
  from digitalkin.logger import logger
24
25
  from digitalkin.models.module.module import ModuleCodeModel
25
26
  from digitalkin.models.module.setup_types import SetupModel
@@ -31,6 +32,7 @@ from digitalkin.services.registry import GrpcRegistry, RegistryStrategy
31
32
  from digitalkin.services.setup.default_setup import DefaultSetup
32
33
  from digitalkin.services.setup.grpc_setup import GrpcSetup
33
34
  from digitalkin.services.setup.setup_strategy import SetupStrategy, SetupVersionData
35
+ from digitalkin.services.user_profile import DefaultUserProfile, GrpcUserProfile, UserProfileStrategy
34
36
  from digitalkin.utils.arg_parser import ArgParser
35
37
  from digitalkin.utils.development_mode_action import DevelopmentModeMappingAction
36
38
 
@@ -40,6 +42,7 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer, ArgParser):
40
42
 
41
43
  args: Namespace
42
44
  setup: SetupStrategy
45
+ user_profile: UserProfileStrategy
43
46
  job_manager: BaseJobManager
44
47
  _registry_cache: RegistryStrategy | None
45
48
  _tool_cache_by_setup: dict[str, tuple[Any, float]]
@@ -82,6 +85,16 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer, ArgParser):
82
85
 
83
86
  logger.debug("ModuleServicer initialized with SingleJobManager")
84
87
  self.setup = GrpcSetup() if self.args.services_mode == ServicesMode.REMOTE else DefaultSetup()
88
+ # Access-control client gating the setup cache. Always built, always called (fail-closed).
89
+ if self.args.services_mode == ServicesMode.REMOTE:
90
+ up_cfg = self.module_class.services_config_params.get("user_profile") or {}
91
+ up_client_config = up_cfg.get("client_config")
92
+ if not up_client_config:
93
+ msg = "user_profile client_config is required for setup access control"
94
+ raise RuntimeError(msg)
95
+ self.user_profile = GrpcUserProfile("", "", "", up_client_config)
96
+ else:
97
+ self.user_profile = DefaultUserProfile("", "", "")
85
98
  self._setup_cache: dict[str, tuple[float, SetupVersionData]] = {}
86
99
  self._setup_inflight: dict[str, asyncio.Future[SetupVersionData]] = {}
87
100
 
@@ -97,6 +110,11 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer, ArgParser):
97
110
  await self.setup.close_channel()
98
111
  except Exception:
99
112
  logger.exception("Error closing GrpcSetup channel")
113
+ if isinstance(self.user_profile, GrpcUserProfile):
114
+ try:
115
+ await self.user_profile.close_channel()
116
+ except Exception:
117
+ logger.exception("Error closing GrpcUserProfile channel")
100
118
  # M8: close the Redis connection pools (were leaked on every server stop).
101
119
  logger.info("[VALIDATE M8] closing module servicer Redis pools") # TODO(validate): remove after prod validation
102
120
  await self._redis_client.close()
@@ -245,6 +263,19 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer, ArgParser):
245
263
  del self._setup_cache[oldest_key]
246
264
  self._setup_cache[setup_id] = (time.monotonic(), version_data)
247
265
 
266
+ async def _check_setup_access(self, setup_id: str) -> None:
267
+ """Block if the caller may not access the setup (RESOURCE_TYPE_SETUP).
268
+
269
+ Args:
270
+ setup_id: The setup identifier being resolved.
271
+
272
+ Raises:
273
+ PermissionDeniedError: If access to the setup is denied.
274
+ """
275
+ if not await self.user_profile.check_resource_access(user_profile_pb2.RESOURCE_TYPE_SETUP, setup_id):
276
+ msg = f"access denied to setup {setup_id}"
277
+ raise PermissionDeniedError(msg)
278
+
248
279
  async def resolve_setup(self, setup_id: str, mission_id: str) -> SetupVersionData:
249
280
  """Return setup version data from cache or remote service.
250
281
 
@@ -257,7 +288,9 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer, ArgParser):
257
288
 
258
289
  Raises:
259
290
  LookupError: No setup data found for setup_id.
291
+ PermissionDeniedError: If the caller may not access this setup.
260
292
  """
293
+ await self._check_setup_access(setup_id)
261
294
  # Fast path: cache hit within TTL
262
295
  if (cached := self._setup_cache.get(setup_id)) is not None:
263
296
  if time.monotonic() - cached[0] < get_module_servicer_settings().setup_cache_ttl:
@@ -326,6 +359,12 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer, ArgParser):
326
359
  extra={"mission_id": request.mission_id},
327
360
  )
328
361
  setup_version = request.setup_version
362
+ if not await self.user_profile.check_resource_access(
363
+ user_profile_pb2.RESOURCE_TYPE_SETUP, setup_version.setup_id
364
+ ):
365
+ context.set_code(grpc.StatusCode.PERMISSION_DENIED)
366
+ context.set_details(f"access denied to setup {setup_version.setup_id}")
367
+ return lifecycle_pb2.ConfigSetupModuleResponse(success=False)
329
368
  # Invalidate cached setup so concurrent/subsequent starts refetch the reconfigured version
330
369
  self._setup_cache.pop(setup_version.setup_id, None)
331
370
  config_setup_data = self.module_class.create_config_setup_model(json_format.MessageToDict(request.content))
@@ -16,6 +16,7 @@ import grpc.aio
16
16
 
17
17
  from digitalkin.core.resilience.bulkhead import Bulkhead
18
18
  from digitalkin.grpc_servers.exceptions import CircuitOpenError, ServerError
19
+ from digitalkin.grpc_servers.interceptors.permission import PermissionClientInterceptor
19
20
  from digitalkin.grpc_servers.interceptors.request_ids import RequestIdClientInterceptor
20
21
  from digitalkin.grpc_servers.utils.circuit_breaker import CircuitBreaker
21
22
  from digitalkin.logger import logger
@@ -103,7 +104,7 @@ class GrpcClientWrapper:
103
104
 
104
105
  credentials = self._build_channel_credentials(config)
105
106
  grpc_compression = config.compression.to_grpc()
106
- interceptors = [RequestIdClientInterceptor()]
107
+ interceptors = [RequestIdClientInterceptor(), PermissionClientInterceptor()]
107
108
  if credentials is not None:
108
109
  channel = grpc.aio.secure_channel(
109
110
  config.address,
@@ -4,7 +4,7 @@ from collections.abc import AsyncGenerator
4
4
  from contextlib import asynccontextmanager
5
5
  from typing import Any
6
6
 
7
- from digitalkin.grpc_servers.exceptions import ServerError
7
+ from digitalkin.grpc_servers.exceptions import PermissionDeniedError, ServerError
8
8
  from digitalkin.logger import logger
9
9
 
10
10
 
@@ -28,6 +28,7 @@ class GrpcErrorHandlerMixin:
28
28
  Context for the operation.
29
29
 
30
30
  Raises:
31
+ PermissionDeniedError: Re-raised as-is so the authz status is never masked.
31
32
  ServerError: For gRPC-related errors.
32
33
  service_error_class: For service-specific errors if provided.
33
34
  """
@@ -36,6 +37,8 @@ class GrpcErrorHandlerMixin:
36
37
 
37
38
  try:
38
39
  yield
40
+ except PermissionDeniedError:
41
+ raise
39
42
  except service_error_class as e:
40
43
  # Re-raise service-specific errors as-is
41
44
  msg = f"{service_error_class.__name__} in {operation}: {e}"
@@ -23,3 +23,4 @@ class StreamErrorCode(str, Enum):
23
23
  MODULE_RUNTIME_ERROR = "MODULE_RUNTIME_ERROR"
24
24
  INPUT_VALIDATION_ERROR = "INPUT_VALIDATION_ERROR"
25
25
  BACKPRESSURE_TIMEOUT = "BACKPRESSURE_TIMEOUT"
26
+ SETUP_ACCESS_DENIED = "SETUP_ACCESS_DENIED"
@@ -17,6 +17,7 @@ from digitalkin.services.cost.cost_strategy import CostStrategy
17
17
  from digitalkin.services.filesystem.filesystem_strategy import FilesystemStrategy
18
18
  from digitalkin.services.identity.identity_strategy import IdentityStrategy
19
19
  from digitalkin.services.registry.registry_strategy import RegistryStrategy
20
+ from digitalkin.services.secret.secret_strategy import SecretStrategy
20
21
  from digitalkin.services.storage.storage_strategy import StorageStrategy
21
22
  from digitalkin.services.task_manager.task_manager_strategy import TaskManagerStrategy
22
23
  from digitalkin.services.user_profile.user_profile_strategy import UserProfileStrategy
@@ -95,6 +96,7 @@ class ModuleContext:
95
96
  filesystem: FilesystemStrategy
96
97
  identity: IdentityStrategy
97
98
  registry: RegistryStrategy
99
+ secret: SecretStrategy
98
100
  storage: StorageStrategy
99
101
  task_manager: TaskManagerStrategy | None
100
102
  user_profile: UserProfileStrategy
@@ -115,6 +117,7 @@ class ModuleContext:
115
117
  filesystem: FilesystemStrategy,
116
118
  identity: IdentityStrategy,
117
119
  registry: RegistryStrategy,
120
+ secret: SecretStrategy,
118
121
  storage: StorageStrategy,
119
122
  user_profile: UserProfileStrategy,
120
123
  session: dict[str, Any],
@@ -135,6 +138,7 @@ class ModuleContext:
135
138
  filesystem: FilesystemStrategy.
136
139
  identity: IdentityStrategy.
137
140
  registry: RegistryStrategy.
141
+ secret: SecretStrategy.
138
142
  storage: StorageStrategy.
139
143
  user_profile: UserProfileStrategy.
140
144
  task_manager: Optional, injected by SingleJobManager (RedisTaskManager).
@@ -153,6 +157,7 @@ class ModuleContext:
153
157
  self.filesystem = filesystem
154
158
  self.identity = identity
155
159
  self.registry = registry
160
+ self.secret = secret
156
161
  self.storage = storage
157
162
  self.task_manager = task_manager
158
163
  self.user_profile = user_profile
@@ -374,6 +379,7 @@ class ModuleContext:
374
379
  ("registry", self.registry),
375
380
  ("filesystem", self.filesystem),
376
381
  ("user_profile", self.user_profile),
382
+ ("secret", self.secret),
377
383
  ("identity", self.identity),
378
384
  )
379
385
  for name, service in owned:
@@ -7,6 +7,7 @@ from abc import ABC, abstractmethod
7
7
  from collections.abc import Callable, Coroutine
8
8
  from typing import Any, ClassVar, Generic
9
9
 
10
+ from digitalkin.grpc_servers.exceptions import PermissionDeniedError
10
11
  from digitalkin.grpc_servers.utils.utility_schema_extender import UtilitySchemaExtender
11
12
  from digitalkin.logger import logger
12
13
  from digitalkin.models.module.module import ModuleCodeModel, ModuleStatus
@@ -548,6 +549,10 @@ class BaseModule( # Module SDK base class requires many public methods # noqa:
548
549
  self._status = ModuleStatus.CANCELLED
549
550
  logger.info("Module %s cancelled", self.name, extra=self.context.session.current_ids())
550
551
  raise
552
+ except PermissionDeniedError as e:
553
+ self._status = ModuleStatus.FAILED
554
+ logger.warning("Permission denied in module %s: %s", self.name, e, extra=self.context.session.current_ids())
555
+ await self._notify_permission_denied(self.context.callbacks.send_message, e)
551
556
  except Exception as e:
552
557
  self._status = ModuleStatus.FAILED
553
558
  logger.exception("Error inside module %s", self.name, extra=self.context.session.current_ids())
@@ -564,6 +569,28 @@ class BaseModule( # Module SDK base class requires many public methods # noqa:
564
569
  else:
565
570
  self._status = ModuleStatus.STOPPING
566
571
 
572
+ async def _notify_permission_denied(
573
+ self,
574
+ callback: Callable[..., Coroutine[Any, Any, None]],
575
+ e: PermissionDeniedError,
576
+ ) -> None:
577
+ """Send a PermissionDenied notification to the user via the callback.
578
+
579
+ Args:
580
+ callback: The output callback installed on the module context.
581
+ e: The permission-denied error raised by a service call.
582
+ """
583
+ try:
584
+ await callback(
585
+ ModuleCodeModel(
586
+ code="PermissionDenied",
587
+ short_description="Permission denied",
588
+ message=str(e),
589
+ )
590
+ )
591
+ except Exception:
592
+ logger.exception("Failed to send permission-denied callback", extra=self.context.session.current_ids())
593
+
567
594
  async def prepare(
568
595
  self,
569
596
  setup_data: SetupModelT,
@@ -623,6 +650,14 @@ class BaseModule( # Module SDK base class requires many public methods # noqa:
623
650
  try:
624
651
  await self.prepare(setup_data, callback)
625
652
  timer.mark("prepare")
653
+ except PermissionDeniedError as e:
654
+ self._status = ModuleStatus.FAILED
655
+ logger.warning("Permission denied initializing module: %s", e, extra=self.context.session.current_ids())
656
+ await self._notify_permission_denied(callback, e)
657
+ if done_callback is not None:
658
+ await done_callback(None)
659
+ await self.stop()
660
+ return
626
661
  except Exception as e:
627
662
  self._status = ModuleStatus.FAILED
628
663
  short_description = "Error initializing module"
@@ -0,0 +1,13 @@
1
+ """Secret service package."""
2
+
3
+ from digitalkin.services.secret.default_secret import DefaultSecret
4
+ from digitalkin.services.secret.exceptions import SecretServiceError
5
+ from digitalkin.services.secret.grpc_secret import GrpcSecret
6
+ from digitalkin.services.secret.secret_strategy import SecretStrategy
7
+
8
+ __all__ = [
9
+ "DefaultSecret",
10
+ "GrpcSecret",
11
+ "SecretServiceError",
12
+ "SecretStrategy",
13
+ ]
@@ -0,0 +1,45 @@
1
+ """Default secret implementation."""
2
+
3
+ from typing import Any
4
+
5
+ from digitalkin.logger import logger
6
+ from digitalkin.services.secret.secret_strategy import SecretStrategy
7
+
8
+
9
+ class DefaultSecret(SecretStrategy):
10
+ """Default secret strategy with in-memory storage."""
11
+
12
+ def __init__(
13
+ self,
14
+ mission_id: str,
15
+ setup_id: str,
16
+ setup_version_id: str,
17
+ ) -> None:
18
+ """Initialize the strategy.
19
+
20
+ Args:
21
+ mission_id: The ID of the mission this strategy is associated with
22
+ setup_id: The ID of the setup
23
+ setup_version_id: The ID of the setup version
24
+ """
25
+ super().__init__(mission_id=mission_id, setup_id=setup_id, setup_version_id=setup_version_id)
26
+ self.db: dict[str, dict[str, Any]] = {}
27
+
28
+ async def get_secret(self) -> dict[str, Any] | None:
29
+ """Get the secret for this setup from in-memory storage.
30
+
31
+ Returns:
32
+ Secret values, or None if not found.
33
+ """
34
+ if self.setup_id not in self.db:
35
+ logger.warning("No secret found for setup_id: %s", self.setup_id)
36
+ return None
37
+ return self.db[self.setup_id]
38
+
39
+ def add_secret(self, secret_data: dict[str, Any]) -> None:
40
+ """Add a secret to the in-memory database (helper for testing).
41
+
42
+ Args:
43
+ secret_data: Dictionary containing secret values.
44
+ """
45
+ self.db[self.setup_id] = secret_data
@@ -0,0 +1,5 @@
1
+ """Exceptions for the secret service."""
2
+
3
+
4
+ class SecretServiceError(Exception):
5
+ """Base exception for Secret service errors."""
@@ -0,0 +1,63 @@
1
+ """Digital Kin Secret Service gRPC Client (wraps UserProfileService.GetSetupSecret)."""
2
+
3
+ from typing import Any
4
+
5
+ from agentic_mesh_protocol.user_profile.v1 import (
6
+ user_profile_pb2,
7
+ user_profile_service_pb2_grpc,
8
+ )
9
+
10
+ from digitalkin.grpc_servers.utils.grpc_client_wrapper import GrpcClientWrapper
11
+ from digitalkin.grpc_servers.utils.grpc_error_handler import GrpcErrorHandlerMixin
12
+ from digitalkin.logger import logger
13
+ from digitalkin.models.grpc_servers.models import ClientConfig
14
+ from digitalkin.services.secret.exceptions import SecretServiceError
15
+ from digitalkin.services.secret.secret_strategy import SecretStrategy
16
+ from digitalkin.utils.proto_utils import ProtoUtils
17
+
18
+
19
+ class GrpcSecret(SecretStrategy, GrpcClientWrapper, GrpcErrorHandlerMixin):
20
+ """gRPC client for setup secrets (backed by the UserProfileService)."""
21
+
22
+ service_name: str = "UserProfileService"
23
+
24
+ def __init__(
25
+ self,
26
+ mission_id: str,
27
+ setup_id: str,
28
+ setup_version_id: str,
29
+ client_config: ClientConfig,
30
+ ) -> None:
31
+ """Initialize the secret service.
32
+
33
+ Args:
34
+ mission_id: The ID of the mission this strategy is associated with
35
+ setup_id: The ID of the setup
36
+ setup_version_id: The ID of the setup version
37
+ client_config: Client configuration for gRPC connection
38
+ """
39
+ super().__init__(mission_id=mission_id, setup_id=setup_id, setup_version_id=setup_version_id)
40
+ self._init_channel(client_config)
41
+ self.stub = self._get_or_create_stub(user_profile_service_pb2_grpc.UserProfileServiceStub)
42
+ logger.debug("Channel client 'Secret' initialized successfully")
43
+
44
+ async def close(self) -> None:
45
+ """Release this instance's pooled gRPC channel ref."""
46
+ await self.close_channel()
47
+
48
+ async def get_secret(self) -> dict[str, Any] | None:
49
+ """Resolve the secret object attached to this setup.
50
+
51
+ Returns:
52
+ The secret values, or None if not found.
53
+
54
+ Raises:
55
+ SecretServiceError: If the gRPC operation fails.
56
+ """
57
+ async with self.handle_grpc_errors("GetSetupSecret", SecretServiceError):
58
+ request = user_profile_pb2.GetSetupSecretRequest(setup_id=self.setup_id, mission_id=self.mission_id)
59
+ response = await self.exec_grpc_query("GetSetupSecret", request)
60
+ if not response.success:
61
+ logger.warning("No secret found for setup_id: %s", self.setup_id)
62
+ return None
63
+ return ProtoUtils.proto_to_dict(response.secret, with_defaults=True)
@@ -0,0 +1,21 @@
1
+ """This module contains the abstract base class for Secret strategies."""
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import Any
5
+
6
+ from digitalkin.services.base_strategy import BaseStrategy
7
+
8
+
9
+ class SecretStrategy(BaseStrategy, ABC):
10
+ """Abstract base class for Secret strategies."""
11
+
12
+ @abstractmethod
13
+ async def get_secret(self) -> dict[str, Any] | None:
14
+ """Resolve the secret object attached to this setup.
15
+
16
+ Returns:
17
+ The secret values (matching the module's secret_schema), or None if not found.
18
+
19
+ Raises:
20
+ SecretServiceError: If the service call fails.
21
+ """
@@ -10,6 +10,7 @@ from digitalkin.services.cost import CostStrategy, DefaultCost, GrpcCost
10
10
  from digitalkin.services.filesystem import DefaultFilesystem, FilesystemStrategy, GrpcFilesystem
11
11
  from digitalkin.services.identity import DefaultIdentity, IdentityStrategy
12
12
  from digitalkin.services.registry import DefaultRegistry, GrpcRegistry, RegistryStrategy
13
+ from digitalkin.services.secret import DefaultSecret, GrpcSecret, SecretStrategy
13
14
  from digitalkin.services.services_models import ServicesStrategy
14
15
  from digitalkin.services.storage import DefaultStorage, GrpcStorage, StorageStrategy
15
16
  from digitalkin.services.user_profile import DefaultUserProfile, GrpcUserProfile, UserProfileStrategy
@@ -36,6 +37,7 @@ class ServicesConfig(BaseModel):
36
37
  "identity",
37
38
  "communication",
38
39
  "user_profile",
40
+ "secret",
39
41
  }
40
42
 
41
43
  def __init__(
@@ -67,6 +69,7 @@ class ServicesConfig(BaseModel):
67
69
  "identity": ServicesStrategy(local=DefaultIdentity, remote=DefaultIdentity),
68
70
  "communication": ServicesStrategy(local=DefaultCommunication, remote=GrpcCommunication),
69
71
  "user_profile": ServicesStrategy(local=DefaultUserProfile, remote=GrpcUserProfile),
72
+ "secret": ServicesStrategy(local=DefaultSecret, remote=GrpcSecret),
70
73
  }
71
74
 
72
75
  # Apply strategy overrides
@@ -162,6 +165,11 @@ class ServicesConfig(BaseModel):
162
165
  """The user_profile service strategy class for the current mode."""
163
166
  return self._strategies["user_profile"][self.mode.value]
164
167
 
168
+ @property
169
+ def secret(self) -> type[SecretStrategy]:
170
+ """The secret service strategy class for the current mode."""
171
+ return self._strategies["secret"][self.mode.value]
172
+
165
173
  def update_mode(self, mode: ServicesMode) -> None:
166
174
  """Update the strategy mode.
167
175
 
@@ -11,6 +11,7 @@ from digitalkin.services.cost import CostStrategy
11
11
  from digitalkin.services.filesystem import FilesystemStrategy
12
12
  from digitalkin.services.identity import IdentityStrategy
13
13
  from digitalkin.services.registry import RegistryStrategy
14
+ from digitalkin.services.secret import SecretStrategy
14
15
  from digitalkin.services.storage import StorageStrategy
15
16
  from digitalkin.services.task_manager.task_manager_strategy import TaskManagerStrategy
16
17
  from digitalkin.services.user_profile import UserProfileStrategy
@@ -23,6 +24,7 @@ T = TypeVar(
23
24
  | FilesystemStrategy
24
25
  | IdentityStrategy
25
26
  | RegistryStrategy
27
+ | SecretStrategy
26
28
  | StorageStrategy
27
29
  | UserProfileStrategy
28
30
  | TaskManagerStrategy,