voodoo-framework 2.1.0__tar.gz → 2.2.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 (219) hide show
  1. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/PKG-INFO +1 -1
  2. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/__init__.py +13 -1
  3. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/primitives/effect.py +9 -0
  4. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/runtime/capability.py +54 -4
  5. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/runtime/engine.py +23 -4
  6. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/security/__init__.py +26 -0
  7. voodoo_framework-2.2.0/src/voodoo/security/redaction.py +175 -0
  8. voodoo_framework-2.2.0/src/voodoo/security/secrets.py +223 -0
  9. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo_framework.egg-info/PKG-INFO +1 -1
  10. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo_framework.egg-info/SOURCES.txt +3 -0
  11. voodoo_framework-2.2.0/tests/test_capability_security.py +426 -0
  12. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/tests/test_contract_api.py +11 -0
  13. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/LICENSE +0 -0
  14. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/README.md +0 -0
  15. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/pyproject.toml +0 -0
  16. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/setup.cfg +0 -0
  17. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/adapters/__init__.py +0 -0
  18. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/adapters/capabilities.py +0 -0
  19. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/adapters/registry.py +0 -0
  20. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/adapters/tailwind.py +0 -0
  21. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/adapters/voodoo_css.py +0 -0
  22. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/agent.py +0 -0
  23. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/agents/__init__.py +0 -0
  24. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/agents/models.py +0 -0
  25. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/agents/registry.py +0 -0
  26. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/ai/__init__.py +0 -0
  27. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/ai/agent.py +0 -0
  28. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/ai/providers/__init__.py +0 -0
  29. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/ai/providers/anthropic.py +0 -0
  30. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/ai/providers/gemini.py +0 -0
  31. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/ai/providers/mock.py +0 -0
  32. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/ai/providers/ollama.py +0 -0
  33. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/ai/providers/openai.py +0 -0
  34. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/ai/tools/__init__.py +0 -0
  35. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/ai/tools/registry.py +0 -0
  36. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/api.py +0 -0
  37. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/auth/__init__.py +0 -0
  38. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/auth/cookies.py +0 -0
  39. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/auth/guards.py +0 -0
  40. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/auth/jwt.py +0 -0
  41. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/auth/middleware.py +0 -0
  42. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/auth/passwords.py +0 -0
  43. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/auth/user.py +0 -0
  44. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/cli/__init__.py +0 -0
  45. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/cli/__main__.py +0 -0
  46. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/cli/agents.py +0 -0
  47. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/cli/ai.py +0 -0
  48. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/cli/approvals.py +0 -0
  49. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/cli/auth.py +0 -0
  50. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/cli/dev.py +0 -0
  51. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/cli/doctor.py +0 -0
  52. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/cli/executions.py +0 -0
  53. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/cli/generate.py +0 -0
  54. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/cli/inspect.py +0 -0
  55. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/cli/new.py +0 -0
  56. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/cli/objects.py +0 -0
  57. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/cli/recover.py +0 -0
  58. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/cli/routes.py +0 -0
  59. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/cli/scaffolding.py +0 -0
  60. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/cli/schedules.py +0 -0
  61. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/cli/tasks.py +0 -0
  62. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/cli/terminal.py +0 -0
  63. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/cli/theme.py +0 -0
  64. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/cli/version.py +0 -0
  65. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/components.py +0 -0
  66. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/config.py +0 -0
  67. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/core/__init__.py +0 -0
  68. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/core/app.py +0 -0
  69. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/core/errors.py +0 -0
  70. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/core/events.py +0 -0
  71. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/core/render.py +0 -0
  72. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/core/routing.py +0 -0
  73. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/core/sitemap.py +0 -0
  74. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/core/state.py +0 -0
  75. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/data/__init__.py +0 -0
  76. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/data/base.py +0 -0
  77. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/data/model.py +0 -0
  78. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/i18n.py +0 -0
  79. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/mcp/__init__.py +0 -0
  80. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/mcp/client.py +0 -0
  81. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/memory/__init__.py +0 -0
  82. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/memory/interfaces.py +0 -0
  83. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/memory/sqlite.py +0 -0
  84. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/mesh/__init__.py +0 -0
  85. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/mesh/client.py +0 -0
  86. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/mesh/event_bus.py +0 -0
  87. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/primitives/__init__.py +0 -0
  88. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/primitives/capability.py +0 -0
  89. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/primitives/compute.py +0 -0
  90. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/primitives/constraint.py +0 -0
  91. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/primitives/intent.py +0 -0
  92. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/primitives/resource.py +0 -0
  93. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/primitives/state.py +0 -0
  94. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/primitives/time.py +0 -0
  95. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/py.typed +0 -0
  96. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/queue.py +0 -0
  97. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/routing/api.py +0 -0
  98. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/routing/pages.py +0 -0
  99. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/runtime/__init__.py +0 -0
  100. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/runtime/adaptive.py +0 -0
  101. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/runtime/constraint.py +0 -0
  102. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/runtime/context.py +0 -0
  103. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/runtime/errors.py +0 -0
  104. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/runtime/execution.py +0 -0
  105. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/runtime/graph.py +0 -0
  106. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/runtime/human.py +0 -0
  107. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/runtime/persistence.py +0 -0
  108. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/runtime/planner.py +0 -0
  109. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/runtime/scheduler.py +0 -0
  110. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/runtime/task.py +0 -0
  111. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/runtime/workflow.py +0 -0
  112. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/schedule.py +0 -0
  113. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/security/cors.py +0 -0
  114. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/security/csrf.py +0 -0
  115. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/security/headers.py +0 -0
  116. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/security/passwords.py +0 -0
  117. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/security/rate_limit.py +0 -0
  118. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/seo.py +0 -0
  119. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/static/client.js +0 -0
  120. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/status.py +0 -0
  121. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/storage/__init__.py +0 -0
  122. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/storage/cache/__init__.py +0 -0
  123. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/storage/cache/interfaces.py +0 -0
  124. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/storage/cache/memory.py +0 -0
  125. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/storage/cache/redis.py +0 -0
  126. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/storage/database/__init__.py +0 -0
  127. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/storage/database/interfaces.py +0 -0
  128. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/storage/database/postgres.py +0 -0
  129. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/storage/database/sqlite.py +0 -0
  130. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/storage/events/__init__.py +0 -0
  131. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/storage/events/interfaces.py +0 -0
  132. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/storage/events/local.py +0 -0
  133. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/storage/events/postgres.py +0 -0
  134. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/storage/events/sqlite.py +0 -0
  135. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/storage/execution/__init__.py +0 -0
  136. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/storage/execution/migrations.py +0 -0
  137. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/storage/execution/postgres.py +0 -0
  138. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/storage/execution/sqlite.py +0 -0
  139. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/storage/manager.py +0 -0
  140. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/storage/objects/__init__.py +0 -0
  141. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/storage/objects/interfaces.py +0 -0
  142. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/storage/objects/local.py +0 -0
  143. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/storage/objects/s3.py +0 -0
  144. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/storage/queue/__init__.py +0 -0
  145. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/storage/queue/interfaces.py +0 -0
  146. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/storage/queue/memory.py +0 -0
  147. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/storage/queue/postgres.py +0 -0
  148. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/storage/queue/redis.py +0 -0
  149. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/storage/queue/sqlite.py +0 -0
  150. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/storage/scheduler/__init__.py +0 -0
  151. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/storage/scheduler/sqlite.py +0 -0
  152. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/telemetry/__init__.py +0 -0
  153. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/telemetry/api.py +0 -0
  154. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/telemetry/middleware.py +0 -0
  155. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/telemetry/store.py +0 -0
  156. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/theme.py +0 -0
  157. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/tools/__init__.py +0 -0
  158. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/tools/registry.py +0 -0
  159. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/ui/__init__.py +0 -0
  160. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/ui/component.py +0 -0
  161. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/ui/events.py +0 -0
  162. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/ui/library.py +0 -0
  163. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/ui/markdown.py +0 -0
  164. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/ui/rendering.py +0 -0
  165. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/ui/state.py +0 -0
  166. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/ui/styles/__init__.py +0 -0
  167. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/ui/styles/presets.py +0 -0
  168. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/ui/styles/theme.py +0 -0
  169. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/workers/__init__.py +0 -0
  170. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo/workers/queue.py +0 -0
  171. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo_framework.egg-info/dependency_links.txt +0 -0
  172. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo_framework.egg-info/entry_points.txt +0 -0
  173. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo_framework.egg-info/requires.txt +0 -0
  174. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/src/voodoo_framework.egg-info/top_level.txt +0 -0
  175. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/tests/test_agent.py +0 -0
  176. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/tests/test_agent_registry.py +0 -0
  177. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/tests/test_agent_runtime.py +0 -0
  178. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/tests/test_app.py +0 -0
  179. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/tests/test_auth.py +0 -0
  180. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/tests/test_checkpoint_resume.py +0 -0
  181. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/tests/test_chrome.py +0 -0
  182. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/tests/test_cli.py +0 -0
  183. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/tests/test_cli_inspect.py +0 -0
  184. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/tests/test_components.py +0 -0
  185. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/tests/test_config.py +0 -0
  186. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/tests/test_data.py +0 -0
  187. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/tests/test_design_system.py +0 -0
  188. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/tests/test_durable_hitl.py +0 -0
  189. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/tests/test_events.py +0 -0
  190. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/tests/test_execution_sqlite_store.py +0 -0
  191. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/tests/test_http_runtime.py +0 -0
  192. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/tests/test_human.py +0 -0
  193. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/tests/test_i18n.py +0 -0
  194. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/tests/test_integration.py +0 -0
  195. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/tests/test_mcp.py +0 -0
  196. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/tests/test_mcp_runtime.py +0 -0
  197. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/tests/test_memory.py +0 -0
  198. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/tests/test_mesh.py +0 -0
  199. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/tests/test_mesh_event_bus.py +0 -0
  200. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/tests/test_model.py +0 -0
  201. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/tests/test_object_store.py +0 -0
  202. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/tests/test_persistence.py +0 -0
  203. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/tests/test_planner_adaptive.py +0 -0
  204. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/tests/test_primitives.py +0 -0
  205. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/tests/test_provider_switching.py +0 -0
  206. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/tests/test_providers.py +0 -0
  207. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/tests/test_queue.py +0 -0
  208. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/tests/test_reactive.py +0 -0
  209. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/tests/test_runtime.py +0 -0
  210. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/tests/test_scheduler.py +0 -0
  211. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/tests/test_security.py +0 -0
  212. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/tests/test_seo.py +0 -0
  213. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/tests/test_state.py +0 -0
  214. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/tests/test_theme.py +0 -0
  215. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/tests/test_theme_presets.py +0 -0
  216. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/tests/test_tools.py +0 -0
  217. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/tests/test_ui.py +0 -0
  218. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/tests/test_websocket.py +0 -0
  219. {voodoo_framework-2.1.0 → voodoo_framework-2.2.0}/tests/test_workers.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: voodoo-framework
3
- Version: 2.1.0
3
+ Version: 2.2.0
4
4
  Summary: The programmable runtime for adaptive applications and operational systems — web, APIs, agents, workers, human workflows, distributed systems, and physical systems in one runtime.
5
5
  Author-email: Helder Perez <contact@helderperez.com>
6
6
  Maintainer-email: Helder Perez <contact@helderperez.com>
@@ -102,7 +102,7 @@ from .ui.styles import (
102
102
  from .ui.styles.theme import Theme, ThemeColors, create_theme
103
103
  from .workers import task
104
104
 
105
- __version__ = "2.1.0"
105
+ __version__ = "2.2.0"
106
106
 
107
107
  __all__ = [
108
108
  # Core runtime
@@ -306,6 +306,18 @@ _DEPRECATED_EXPORTS: dict[str, str] = {
306
306
  "rate_limiter": "voodoo.security",
307
307
  "set_csrf_cookie": "voodoo.security",
308
308
  "validate_password_strength": "voodoo.security",
309
+ # voodoo.security — Sprint 19
310
+ "SecretStore": "voodoo.security",
311
+ "EnvSecretStore": "voodoo.security",
312
+ "LocalSecretStore": "voodoo.security",
313
+ "SecretsError": "voodoo.security",
314
+ "secrets": "voodoo.security",
315
+ "configure_secrets": "voodoo.security",
316
+ "RedactionGuard": "voodoo.security",
317
+ "redact": "voodoo.security",
318
+ "redact_string": "voodoo.security",
319
+ # voodoo.runtime — Sprint 19
320
+ "SENSITIVE_CAPABILITIES": "voodoo.runtime.capability",
309
321
  # voodoo.seo
310
322
  "FAQ": "voodoo.seo",
311
323
  "GEO": "voodoo.seo",
@@ -61,6 +61,11 @@ class Effect(BaseModel):
61
61
  error: str | None = None
62
62
  created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
63
63
  executed_at: datetime | None = None
64
+ # Authorization context (Sprint 19, ROADMAP §55)
65
+ actor: str | None = None
66
+ principal: str | None = None
67
+ resource: str | None = None
68
+ scope: str | None = None
64
69
 
65
70
  # -- lifecycle ---------------------------------------------------------
66
71
 
@@ -114,6 +119,10 @@ class Effect(BaseModel):
114
119
  "status": self.status.value,
115
120
  "intent_id": self.intent_id,
116
121
  "capability": self.capability_name,
122
+ "actor": self.actor,
123
+ "principal": self.principal,
124
+ "resource": self.resource,
125
+ "scope": self.scope,
117
126
  "reversible": self.reversible,
118
127
  "idempotent": self.idempotent,
119
128
  "idempotency_key": self.idempotency_key,
@@ -23,7 +23,23 @@ from voodoo.primitives.capability import Capability
23
23
  from voodoo.runtime.context import ExecutionContext
24
24
  from voodoo.runtime.errors import ApprovalRequired, CapabilityDenied
25
25
 
26
- __all__ = ["Resolution", "CapabilityResolver"]
26
+ __all__ = ["Resolution", "CapabilityResolver", "SENSITIVE_CAPABILITIES"]
27
+
28
+
29
+ #: Capabilities that require explicit grants — no ambient authority by
30
+ #: default (Sprint 19, ROADMAP §55). An agent or tool that attempts to
31
+ #: use one of these without an explicit grant gets a :class:`CapabilityDenied`
32
+ #: error, never a silent pass-through.
33
+ SENSITIVE_CAPABILITIES: frozenset[str] = frozenset(
34
+ {
35
+ "filesystem.write",
36
+ "network.request",
37
+ "shell.execute",
38
+ "secrets.read",
39
+ "payment.execute",
40
+ "email.send",
41
+ }
42
+ )
27
43
 
28
44
 
29
45
  class Resolution(StrEnum):
@@ -65,17 +81,50 @@ class CapabilityResolver:
65
81
  Context-granted capabilities take precedence (they are the
66
82
  delegated authority for an in-flight execution). Otherwise the
67
83
  registry is consulted.
84
+
85
+ Sensitive capabilities (Sprint 19) are denied by default — they
86
+ require an explicit grant in the context or registry.
68
87
  """
88
+ if name in SENSITIVE_CAPABILITIES:
89
+ return self._resolve_sensitive(name, scope=scope, context=context)
90
+ return self._resolve_standard(name, scope=scope, context=context)
91
+
92
+ def _resolve_sensitive(
93
+ self,
94
+ name: str,
95
+ *,
96
+ scope: str | None = None,
97
+ context: ExecutionContext | None = None,
98
+ ) -> Resolution:
99
+ """Resolve a sensitive capability — requires explicit grant."""
69
100
  if context is not None and context.has_capability(name, scope=scope):
70
- if name in self.approval_capabilities:
71
- return Resolution.REQUIRES_APPROVAL
72
- return Resolution.ALLOWED
101
+ return self._check_approval(name)
102
+ cap = self.capabilities.get(name)
103
+ if cap is not None and cap.valid:
104
+ if scope is not None and cap.scope is not None and cap.scope != scope:
105
+ return Resolution.DENIED
106
+ return self._check_approval(name)
107
+ return Resolution.DENIED
73
108
 
109
+ def _resolve_standard(
110
+ self,
111
+ name: str,
112
+ *,
113
+ scope: str | None = None,
114
+ context: ExecutionContext | None = None,
115
+ ) -> Resolution:
116
+ """Resolve a standard capability — normal registry rules."""
117
+ if context is not None and context.has_capability(name, scope=scope):
118
+ return self._check_approval(name)
74
119
  cap = self.capabilities.get(name)
75
120
  if cap is None or not cap.valid:
76
121
  return Resolution.DENIED
77
122
  if scope is not None and cap.scope is not None and cap.scope != scope:
78
123
  return Resolution.DENIED
124
+ return self._check_approval(name)
125
+
126
+ def _check_approval(self, name: str) -> Resolution:
127
+ """Check whether a capability requires human approval."""
79
128
  if name in self.approval_capabilities:
80
129
  return Resolution.REQUIRES_APPROVAL
81
130
  return Resolution.ALLOWED
@@ -114,4 +163,5 @@ class CapabilityResolver:
114
163
  return {
115
164
  "capabilities": [c.name for c in self.capabilities.values()],
116
165
  "approval_required": sorted(self.approval_capabilities),
166
+ "sensitive": sorted(SENSITIVE_CAPABILITIES),
117
167
  }
@@ -173,10 +173,16 @@ class ExecutionEngine:
173
173
  def _journal_approval_decision(
174
174
  self, execution_id: str, event: str, payload: dict
175
175
  ) -> None:
176
- """Record an approval decision as a journal event (Sprint 4)."""
176
+ """Record an approval decision as a journal event (Sprint 4).
177
+
178
+ Applies redaction to prevent secrets from leaking into the
179
+ execution journal (Sprint 19, ROADMAP §55).
180
+ """
177
181
  store = self._execution_store
178
182
  if store is not None and hasattr(store, "append_event"):
179
- store.append_event(execution_id, event, payload)
183
+ from voodoo.security.redaction import redact
184
+
185
+ store.append_event(execution_id, event, redact(payload))
180
186
 
181
187
  def checkpoint(self, execution: Execution) -> None:
182
188
  """Public checkpoint API — persists an execution mid-flight.
@@ -511,6 +517,13 @@ class ExecutionEngine:
511
517
  """Record a compute result's effects, state changes and resources."""
512
518
  for effect in result.effects:
513
519
  effect.intent_id = intent.id
520
+ # Authorization context (Sprint 19, ROADMAP §55): record
521
+ # who triggered the effect, which capability authorized it,
522
+ # and what resource/scope it targets.
523
+ if effect.actor is None:
524
+ effect.actor = ctx.actor
525
+ if effect.capability_name is None and ctx.capabilities:
526
+ effect.capability_name = ctx.capabilities[0].name
514
527
  # Idempotency key: stable per execution+effect so a resumed
515
528
  # execution can safely skip already-completed effects (spec §15).
516
529
  if effect.idempotency_key is None:
@@ -722,15 +735,21 @@ class ExecutionEngine:
722
735
  return out
723
736
 
724
737
  async def _emit(self, event: str, payload: dict[str, Any]) -> None:
725
- """Publish a namespaced mesh event (best-effort, never breaks)."""
738
+ """Publish a namespaced mesh event (best-effort, never breaks).
739
+
740
+ Applies redaction to prevent secrets from leaking into the
741
+ event bus (Sprint 19, ROADMAP §55).
742
+ """
726
743
  try:
727
744
  from voodoo.mesh import mesh
745
+ from voodoo.security.redaction import redact
728
746
 
729
- await mesh.broadcast(event, payload)
747
+ await mesh.broadcast(event, redact(payload))
730
748
  except Exception: # noqa: BLE001
731
749
  pass
732
750
 
733
751
  def _record_telemetry(self, execution: Execution) -> None:
752
+ """Record execution telemetry with redaction (Sprint 19)."""
734
753
  try:
735
754
  from voodoo.telemetry import telemetry_store
736
755
 
@@ -10,6 +10,10 @@ This package re-exports the names that previously lived in the flat
10
10
  - ``voodoo/security/rate_limit.py`` — ``RateLimiter``, ``RateLimitMiddleware``
11
11
  and the shared ``rate_limiter`` singleton
12
12
  - ``voodoo/security/passwords.py`` — ``validate_password_strength``
13
+ - ``voodoo/security/secrets.py`` — ``SecretStore``, ``EnvSecretStore``,
14
+ ``LocalSecretStore``, ``secrets``, ``configure`` (Sprint 19)
15
+ - ``voodoo/security/redaction.py`` — ``redact``, ``redact_string``,
16
+ ``RedactionGuard`` (Sprint 19)
13
17
  """
14
18
 
15
19
  from voodoo.security.cors import CORSMiddleware
@@ -25,6 +29,17 @@ from voodoo.security.rate_limit import (
25
29
  RateLimitMiddleware,
26
30
  rate_limiter,
27
31
  )
32
+ from voodoo.security.redaction import RedactionGuard, redact, redact_string
33
+ from voodoo.security.secrets import (
34
+ EnvSecretStore,
35
+ LocalSecretStore,
36
+ SecretsError,
37
+ SecretStore,
38
+ secrets,
39
+ )
40
+ from voodoo.security.secrets import (
41
+ configure as configure_secrets,
42
+ )
28
43
 
29
44
  __all__ = [
30
45
  # Headers
@@ -41,4 +56,15 @@ __all__ = [
41
56
  "rate_limiter",
42
57
  # Passwords
43
58
  "validate_password_strength",
59
+ # Secrets (Sprint 19)
60
+ "SecretStore",
61
+ "EnvSecretStore",
62
+ "LocalSecretStore",
63
+ "SecretsError",
64
+ "secrets",
65
+ "configure_secrets",
66
+ # Redaction (Sprint 19)
67
+ "RedactionGuard",
68
+ "redact",
69
+ "redact_string",
44
70
  ]
@@ -0,0 +1,175 @@
1
+ """Redaction guard — prevent secrets from leaking into observability (Sprint 19).
2
+
3
+ Centralized redaction for all output channels: events, journal entries,
4
+ telemetry records, mesh payloads, and log output.
5
+
6
+ The guard scrubs:
7
+
8
+ 1. Values of registered secrets (from :mod:`voodoo.security.secrets`).
9
+ 2. Common secret patterns (API keys, bearer tokens, password URIs).
10
+ 3. Keys named ``secret``, ``password``, ``token``, ``api_key``, etc.
11
+
12
+ Usage::
13
+
14
+ from voodoo.security.redaction import redact
15
+
16
+ safe = redact({"api_key": "sk-abc123", "name": "test"})
17
+ # → {"api_key": "[REDACTED]", "name": "test"}
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import re
23
+ from typing import Any
24
+
25
+ __all__ = [
26
+ "redact",
27
+ "redact_string",
28
+ "RedactionGuard",
29
+ ]
30
+
31
+ # ---------------------------------------------------------------------------
32
+ # Patterns
33
+ # ---------------------------------------------------------------------------
34
+
35
+ #: Keys whose values are always redacted (case-insensitive partial match).
36
+ _SENSITIVE_KEY_PATTERNS: re.Pattern[str] = re.compile(
37
+ r"(?i)(secret|password|passwd|token|api[_-]?key|auth|credential|private[_-]?key|"
38
+ r"access[_-]?key|session[_-]?id|cookie)",
39
+ )
40
+
41
+ #: Value patterns that look like secrets regardless of key name.
42
+ _SENSITIVE_VALUE_PATTERNS: list[re.Pattern[str]] = [
43
+ # Bearer tokens
44
+ re.compile(r"(?i)bearer\s+[A-Za-z0-9\-._~+/]+=*"),
45
+ # OpenAI-style keys
46
+ re.compile(r"sk-[A-Za-z0-9]{20,}"),
47
+ # Anthropic-style keys
48
+ re.compile(r"sk-ant-[A-Za-z0-9\-]{20,}"),
49
+ # AWS access keys
50
+ re.compile(r"AKIA[0-9A-Z]{16}"),
51
+ # Generic long hex tokens (32+ chars)
52
+ re.compile(r"\b[A-Fa-f0-9]{32,}\b"),
53
+ # JWT tokens (three base64 segments)
54
+ re.compile(r"eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+"),
55
+ # Password in URI (scheme://user:password@host)
56
+ re.compile(r"(?i)([a-z]+://[^:]+:)[^@\s]{4,}(@)"),
57
+ ]
58
+
59
+ _REDACTED = "[REDACTED]"
60
+
61
+
62
+ def _is_sensitive_key(key: str) -> bool:
63
+ """Whether a dict key name suggests a secret value."""
64
+ return bool(_SENSITIVE_KEY_PATTERNS.search(key))
65
+
66
+
67
+ def _redact_value_pattern(value: str) -> str:
68
+ """Replace known secret patterns inside a string."""
69
+ result = value
70
+ for pattern in _SENSITIVE_VALUE_PATTERNS:
71
+ if pattern.pattern.startswith("(?i)([a-z]+://"):
72
+ # URI password pattern — preserve scheme and host
73
+ result = pattern.sub(rf"\1{_REDACTED}\2", result)
74
+ else:
75
+ result = pattern.sub(_REDACTED, result)
76
+ return result
77
+
78
+
79
+ # ---------------------------------------------------------------------------
80
+ # Redaction guard
81
+ # ---------------------------------------------------------------------------
82
+
83
+
84
+ class RedactionGuard:
85
+ """Centralized redaction engine.
86
+
87
+ Maintains a set of known secret values (resolved from the secrets
88
+ store) and applies key-name + value-pattern matching.
89
+ """
90
+
91
+ def __init__(self) -> None:
92
+ self._known_values: set[str] = set()
93
+
94
+ def load_secrets(self, names: set[str]) -> None:
95
+ """Resolve secret values by name and cache them for redaction.
96
+
97
+ Parameters
98
+ ----------
99
+ names:
100
+ Secret names to resolve from the active secrets store.
101
+ """
102
+ from voodoo.security.secrets import get
103
+
104
+ for name in names:
105
+ value = get(name)
106
+ if value and len(value) >= 4:
107
+ self._known_values.add(value)
108
+
109
+ def add_value(self, value: str) -> None:
110
+ """Manually add a known secret value for redaction."""
111
+ if value and len(value) >= 4:
112
+ self._known_values.add(value)
113
+
114
+ def redact(self, data: Any) -> Any:
115
+ """Recursively redact secrets from a data structure.
116
+
117
+ Handles dicts, lists, strings, and passthrough for other types.
118
+ """
119
+ if isinstance(data, dict):
120
+ return {k: self._redact_field(k, v) for k, v in data.items()}
121
+ if isinstance(data, list):
122
+ return [self.redact(item) for item in data]
123
+ if isinstance(data, str):
124
+ return self.redact_string(data)
125
+ return data
126
+
127
+ def _redact_field(self, key: str, value: Any) -> Any:
128
+ """Redact a single key-value pair."""
129
+ if _is_sensitive_key(key):
130
+ if isinstance(value, str) and value:
131
+ return _REDACTED
132
+ if value is not None:
133
+ return _REDACTED
134
+ return self.redact(value)
135
+
136
+ def redact_string(self, value: str) -> str:
137
+ """Redact known secret values and patterns inside a string."""
138
+ result = value
139
+ # Replace known secret values
140
+ for secret_val in self._known_values:
141
+ if secret_val in result:
142
+ result = result.replace(secret_val, _REDACTED)
143
+ # Replace common patterns
144
+ result = _redact_value_pattern(result)
145
+ return result
146
+
147
+
148
+ # ---------------------------------------------------------------------------
149
+ # Module-level singleton
150
+ # ---------------------------------------------------------------------------
151
+
152
+ _guard = RedactionGuard()
153
+
154
+
155
+ def redact(data: Any) -> Any:
156
+ """Redact secrets from data using the global guard.
157
+
158
+ Convenience wrapper around :meth:`RedactionGuard.redact`.
159
+ """
160
+ return _guard.redact(data)
161
+
162
+
163
+ def redact_string(value: str) -> str:
164
+ """Redact secrets from a string using the global guard."""
165
+ return _guard.redact_string(value)
166
+
167
+
168
+ def load_secrets(names: set[str]) -> None:
169
+ """Load secret values into the global guard for redaction."""
170
+ _guard.load_secrets(names)
171
+
172
+
173
+ def add_known_value(value: str) -> None:
174
+ """Add a known secret value to the global guard."""
175
+ _guard.add_value(value)
@@ -0,0 +1,223 @@
1
+ """Secrets store — centralized secret management (Sprint 19, ROADMAP §55).
2
+
3
+ Provides a single interface for accessing secrets so that:
4
+
5
+ - Secrets are never hardcoded or leaked into events/journal/telemetry.
6
+ - The default backend reads from environment variables (zero-config).
7
+ - An optional encrypted local store is available for development.
8
+ - Provider managers (Vault, AWS Secrets Manager, etc.) are future adapters.
9
+
10
+ Usage::
11
+
12
+ from voodoo.security.secrets import secrets
13
+
14
+ api_key = secrets.get("OPENAI_API_KEY")
15
+ db_pass = secrets.get("DATABASE_PASSWORD")
16
+
17
+ The module-level ``secrets`` singleton is an :class:`EnvSecretStore` by
18
+ default. Call ``configure()`` to swap the backend.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import json
24
+ import os
25
+ from pathlib import Path
26
+ from typing import Protocol, runtime_checkable
27
+
28
+ __all__ = [
29
+ "SecretStore",
30
+ "EnvSecretStore",
31
+ "LocalSecretStore",
32
+ "SecretsError",
33
+ "secrets",
34
+ "configure",
35
+ "registered_names",
36
+ ]
37
+
38
+
39
+ class SecretsError(RuntimeError):
40
+ """Raised when a secret cannot be retrieved."""
41
+
42
+
43
+ # ---------------------------------------------------------------------------
44
+ # Protocol
45
+ # ---------------------------------------------------------------------------
46
+
47
+
48
+ @runtime_checkable
49
+ class SecretStore(Protocol):
50
+ """Minimal interface every secrets backend must satisfy."""
51
+
52
+ def get(self, name: str) -> str | None:
53
+ """Return the secret value, or ``None`` if not found."""
54
+ ...
55
+
56
+ def has(self, name: str) -> bool:
57
+ """Whether the secret exists."""
58
+ ...
59
+
60
+
61
+ # ---------------------------------------------------------------------------
62
+ # Environment-variable backend (default)
63
+ # ---------------------------------------------------------------------------
64
+
65
+
66
+ class EnvSecretStore:
67
+ """Read secrets from environment variables.
68
+
69
+ This is the zero-config default — no external services required.
70
+ """
71
+
72
+ def get(self, name: str) -> str | None:
73
+ return os.environ.get(name)
74
+
75
+ def has(self, name: str) -> bool:
76
+ return name in os.environ
77
+
78
+
79
+ # ---------------------------------------------------------------------------
80
+ # Encrypted local file backend
81
+ # ---------------------------------------------------------------------------
82
+
83
+
84
+ class LocalSecretStore:
85
+ """Encrypted local file store using Fernet symmetric encryption.
86
+
87
+ Requires the ``cryptography`` package (optional extra ``[secrets]``).
88
+ The encryption key is derived from a passphrase or read from a key file.
89
+
90
+ Parameters
91
+ ----------
92
+ path:
93
+ Path to the secrets JSON file.
94
+ key:
95
+ Fernet encryption key. If ``None``, reads from ``VOODOO_SECRETS_KEY``
96
+ env var or ``.voodoo-secrets-key`` file.
97
+ """
98
+
99
+ def __init__(self, path: str | Path, key: str | None = None) -> None:
100
+ self._path = Path(path)
101
+ self._key = key
102
+ self._data: dict[str, str] | None = None
103
+
104
+ def _ensure_key(self) -> str:
105
+ if self._key:
106
+ return self._key
107
+ env_key = os.environ.get("VOODOO_SECRETS_KEY")
108
+ if env_key:
109
+ self._key = env_key
110
+ return env_key
111
+ key_file = Path(".voodoo-secrets-key")
112
+ if key_file.exists():
113
+ self._key = key_file.read_text().strip()
114
+ return self._key
115
+ raise SecretsError(
116
+ "No encryption key found. Set VOODOO_SECRETS_KEY env var, "
117
+ "create .voodoo-secrets-key file, or pass key= to LocalSecretStore."
118
+ )
119
+
120
+ def _load(self) -> dict[str, str]:
121
+ if self._data is not None:
122
+ return self._data
123
+ if not self._path.exists():
124
+ self._data = {}
125
+ return self._data
126
+ try:
127
+ from cryptography.fernet import Fernet
128
+
129
+ key = self._ensure_key()
130
+ f = Fernet(key.encode() if isinstance(key, str) else key)
131
+ raw = self._path.read_bytes()
132
+ decrypted = f.decrypt(raw)
133
+ self._data = json.loads(decrypted)
134
+ except ImportError:
135
+ # cryptography not installed — read as plain JSON
136
+ self._data = json.loads(self._path.read_text())
137
+ except SecretsError:
138
+ # No key available — fall back to plain JSON
139
+ self._data = json.loads(self._path.read_text())
140
+ except Exception: # noqa: BLE001
141
+ # Decryption failed — try plain JSON (dev convenience)
142
+ self._data = json.loads(self._path.read_text())
143
+ return self._data
144
+
145
+ def get(self, name: str) -> str | None:
146
+ return self._load().get(name)
147
+
148
+ def has(self, name: str) -> bool:
149
+ return name in self._load()
150
+
151
+ def set(self, name: str, value: str) -> None:
152
+ """Store a secret (encrypts and writes to disk)."""
153
+ data = self._load()
154
+ data[name] = value
155
+ self._save(data)
156
+
157
+ def _save(self, data: dict[str, str]) -> None:
158
+ self._path.parent.mkdir(parents=True, exist_ok=True)
159
+ try:
160
+ from cryptography.fernet import Fernet
161
+
162
+ key = self._ensure_key()
163
+ f = Fernet(key.encode() if isinstance(key, str) else key)
164
+ encrypted = f.encrypt(json.dumps(data).encode())
165
+ self._path.write_bytes(encrypted)
166
+ except (ImportError, SecretsError):
167
+ self._path.write_text(json.dumps(data, indent=2))
168
+ self._data = data
169
+
170
+
171
+ # ---------------------------------------------------------------------------
172
+ # Module-level singleton
173
+ # ---------------------------------------------------------------------------
174
+
175
+ #: The active secrets store. Defaults to :class:`EnvSecretStore`.
176
+ _store: SecretStore = EnvSecretStore()
177
+
178
+ #: Names of secrets that have been explicitly registered for redaction.
179
+ _registered_names: set[str] = set()
180
+
181
+
182
+ def configure(store: SecretStore) -> None:
183
+ """Swap the active secrets backend.
184
+
185
+ Parameters
186
+ ----------
187
+ store:
188
+ Any object satisfying the :class:`SecretStore` protocol.
189
+ """
190
+ global _store
191
+ if not isinstance(store, SecretStore):
192
+ raise TypeError(
193
+ f"store must satisfy the SecretStore protocol, got {type(store).__name__}"
194
+ )
195
+ _store = store
196
+
197
+
198
+ def get(name: str) -> str | None:
199
+ """Retrieve a secret by name from the active store."""
200
+ return _store.get(name)
201
+
202
+
203
+ def has(name: str) -> bool:
204
+ """Whether a secret exists in the active store."""
205
+ return _store.has(name)
206
+
207
+
208
+ def register_name(name: str) -> None:
209
+ """Register a secret name for redaction tracking.
210
+
211
+ Registered names (and their values, when resolved) are scrubbed
212
+ from events, journal entries, telemetry, and log output.
213
+ """
214
+ _registered_names.add(name)
215
+
216
+
217
+ def registered_names() -> set[str]:
218
+ """Return the set of registered secret names."""
219
+ return set(_registered_names)
220
+
221
+
222
+ # Convenience alias
223
+ secrets = EnvSecretStore()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: voodoo-framework
3
- Version: 2.1.0
3
+ Version: 2.2.0
4
4
  Summary: The programmable runtime for adaptive applications and operational systems — web, APIs, agents, workers, human workflows, distributed systems, and physical systems in one runtime.
5
5
  Author-email: Helder Perez <contact@helderperez.com>
6
6
  Maintainer-email: Helder Perez <contact@helderperez.com>
@@ -110,6 +110,8 @@ src/voodoo/security/csrf.py
110
110
  src/voodoo/security/headers.py
111
111
  src/voodoo/security/passwords.py
112
112
  src/voodoo/security/rate_limit.py
113
+ src/voodoo/security/redaction.py
114
+ src/voodoo/security/secrets.py
113
115
  src/voodoo/static/client.js
114
116
  src/voodoo/storage/__init__.py
115
117
  src/voodoo/storage/manager.py
@@ -171,6 +173,7 @@ tests/test_agent_registry.py
171
173
  tests/test_agent_runtime.py
172
174
  tests/test_app.py
173
175
  tests/test_auth.py
176
+ tests/test_capability_security.py
174
177
  tests/test_checkpoint_resume.py
175
178
  tests/test_chrome.py
176
179
  tests/test_cli.py