voodoo-framework 2.0.0__tar.gz → 2.1.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 (216) hide show
  1. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/PKG-INFO +14 -20
  2. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/README.md +13 -19
  3. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/__init__.py +1 -1
  4. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/cli/__init__.py +2 -0
  5. voodoo_framework-2.1.0/src/voodoo/cli/approvals.py +283 -0
  6. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/runtime/engine.py +64 -2
  7. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/runtime/human.py +8 -0
  8. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/storage/execution/migrations.py +5 -2
  9. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/storage/execution/postgres.py +27 -3
  10. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/storage/execution/sqlite.py +25 -3
  11. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo_framework.egg-info/PKG-INFO +14 -20
  12. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo_framework.egg-info/SOURCES.txt +2 -0
  13. voodoo_framework-2.1.0/tests/test_durable_hitl.py +346 -0
  14. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/LICENSE +0 -0
  15. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/pyproject.toml +0 -0
  16. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/setup.cfg +0 -0
  17. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/adapters/__init__.py +0 -0
  18. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/adapters/capabilities.py +0 -0
  19. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/adapters/registry.py +0 -0
  20. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/adapters/tailwind.py +0 -0
  21. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/adapters/voodoo_css.py +0 -0
  22. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/agent.py +0 -0
  23. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/agents/__init__.py +0 -0
  24. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/agents/models.py +0 -0
  25. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/agents/registry.py +0 -0
  26. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/ai/__init__.py +0 -0
  27. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/ai/agent.py +0 -0
  28. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/ai/providers/__init__.py +0 -0
  29. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/ai/providers/anthropic.py +0 -0
  30. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/ai/providers/gemini.py +0 -0
  31. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/ai/providers/mock.py +0 -0
  32. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/ai/providers/ollama.py +0 -0
  33. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/ai/providers/openai.py +0 -0
  34. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/ai/tools/__init__.py +0 -0
  35. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/ai/tools/registry.py +0 -0
  36. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/api.py +0 -0
  37. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/auth/__init__.py +0 -0
  38. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/auth/cookies.py +0 -0
  39. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/auth/guards.py +0 -0
  40. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/auth/jwt.py +0 -0
  41. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/auth/middleware.py +0 -0
  42. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/auth/passwords.py +0 -0
  43. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/auth/user.py +0 -0
  44. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/cli/__main__.py +0 -0
  45. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/cli/agents.py +0 -0
  46. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/cli/ai.py +0 -0
  47. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/cli/auth.py +0 -0
  48. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/cli/dev.py +0 -0
  49. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/cli/doctor.py +0 -0
  50. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/cli/executions.py +0 -0
  51. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/cli/generate.py +0 -0
  52. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/cli/inspect.py +0 -0
  53. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/cli/new.py +0 -0
  54. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/cli/objects.py +0 -0
  55. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/cli/recover.py +0 -0
  56. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/cli/routes.py +0 -0
  57. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/cli/scaffolding.py +0 -0
  58. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/cli/schedules.py +0 -0
  59. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/cli/tasks.py +0 -0
  60. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/cli/terminal.py +0 -0
  61. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/cli/theme.py +0 -0
  62. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/cli/version.py +0 -0
  63. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/components.py +0 -0
  64. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/config.py +0 -0
  65. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/core/__init__.py +0 -0
  66. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/core/app.py +0 -0
  67. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/core/errors.py +0 -0
  68. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/core/events.py +0 -0
  69. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/core/render.py +0 -0
  70. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/core/routing.py +0 -0
  71. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/core/sitemap.py +0 -0
  72. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/core/state.py +0 -0
  73. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/data/__init__.py +0 -0
  74. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/data/base.py +0 -0
  75. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/data/model.py +0 -0
  76. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/i18n.py +0 -0
  77. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/mcp/__init__.py +0 -0
  78. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/mcp/client.py +0 -0
  79. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/memory/__init__.py +0 -0
  80. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/memory/interfaces.py +0 -0
  81. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/memory/sqlite.py +0 -0
  82. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/mesh/__init__.py +0 -0
  83. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/mesh/client.py +0 -0
  84. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/mesh/event_bus.py +0 -0
  85. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/primitives/__init__.py +0 -0
  86. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/primitives/capability.py +0 -0
  87. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/primitives/compute.py +0 -0
  88. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/primitives/constraint.py +0 -0
  89. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/primitives/effect.py +0 -0
  90. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/primitives/intent.py +0 -0
  91. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/primitives/resource.py +0 -0
  92. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/primitives/state.py +0 -0
  93. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/primitives/time.py +0 -0
  94. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/py.typed +0 -0
  95. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/queue.py +0 -0
  96. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/routing/api.py +0 -0
  97. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/routing/pages.py +0 -0
  98. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/runtime/__init__.py +0 -0
  99. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/runtime/adaptive.py +0 -0
  100. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/runtime/capability.py +0 -0
  101. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/runtime/constraint.py +0 -0
  102. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/runtime/context.py +0 -0
  103. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/runtime/errors.py +0 -0
  104. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/runtime/execution.py +0 -0
  105. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/runtime/graph.py +0 -0
  106. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/runtime/persistence.py +0 -0
  107. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/runtime/planner.py +0 -0
  108. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/runtime/scheduler.py +0 -0
  109. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/runtime/task.py +0 -0
  110. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/runtime/workflow.py +0 -0
  111. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/schedule.py +0 -0
  112. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/security/__init__.py +0 -0
  113. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/security/cors.py +0 -0
  114. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/security/csrf.py +0 -0
  115. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/security/headers.py +0 -0
  116. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/security/passwords.py +0 -0
  117. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/security/rate_limit.py +0 -0
  118. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/seo.py +0 -0
  119. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/static/client.js +0 -0
  120. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/status.py +0 -0
  121. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/storage/__init__.py +0 -0
  122. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/storage/cache/__init__.py +0 -0
  123. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/storage/cache/interfaces.py +0 -0
  124. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/storage/cache/memory.py +0 -0
  125. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/storage/cache/redis.py +0 -0
  126. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/storage/database/__init__.py +0 -0
  127. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/storage/database/interfaces.py +0 -0
  128. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/storage/database/postgres.py +0 -0
  129. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/storage/database/sqlite.py +0 -0
  130. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/storage/events/__init__.py +0 -0
  131. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/storage/events/interfaces.py +0 -0
  132. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/storage/events/local.py +0 -0
  133. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/storage/events/postgres.py +0 -0
  134. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/storage/events/sqlite.py +0 -0
  135. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/storage/execution/__init__.py +0 -0
  136. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/storage/manager.py +0 -0
  137. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/storage/objects/__init__.py +0 -0
  138. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/storage/objects/interfaces.py +0 -0
  139. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/storage/objects/local.py +0 -0
  140. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/storage/objects/s3.py +0 -0
  141. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/storage/queue/__init__.py +0 -0
  142. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/storage/queue/interfaces.py +0 -0
  143. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/storage/queue/memory.py +0 -0
  144. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/storage/queue/postgres.py +0 -0
  145. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/storage/queue/redis.py +0 -0
  146. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/storage/queue/sqlite.py +0 -0
  147. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/storage/scheduler/__init__.py +0 -0
  148. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/storage/scheduler/sqlite.py +0 -0
  149. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/telemetry/__init__.py +0 -0
  150. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/telemetry/api.py +0 -0
  151. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/telemetry/middleware.py +0 -0
  152. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/telemetry/store.py +0 -0
  153. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/theme.py +0 -0
  154. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/tools/__init__.py +0 -0
  155. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/tools/registry.py +0 -0
  156. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/ui/__init__.py +0 -0
  157. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/ui/component.py +0 -0
  158. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/ui/events.py +0 -0
  159. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/ui/library.py +0 -0
  160. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/ui/markdown.py +0 -0
  161. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/ui/rendering.py +0 -0
  162. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/ui/state.py +0 -0
  163. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/ui/styles/__init__.py +0 -0
  164. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/ui/styles/presets.py +0 -0
  165. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/ui/styles/theme.py +0 -0
  166. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/workers/__init__.py +0 -0
  167. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo/workers/queue.py +0 -0
  168. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo_framework.egg-info/dependency_links.txt +0 -0
  169. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo_framework.egg-info/entry_points.txt +0 -0
  170. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo_framework.egg-info/requires.txt +0 -0
  171. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/src/voodoo_framework.egg-info/top_level.txt +0 -0
  172. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/tests/test_agent.py +0 -0
  173. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/tests/test_agent_registry.py +0 -0
  174. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/tests/test_agent_runtime.py +0 -0
  175. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/tests/test_app.py +0 -0
  176. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/tests/test_auth.py +0 -0
  177. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/tests/test_checkpoint_resume.py +0 -0
  178. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/tests/test_chrome.py +0 -0
  179. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/tests/test_cli.py +0 -0
  180. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/tests/test_cli_inspect.py +0 -0
  181. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/tests/test_components.py +0 -0
  182. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/tests/test_config.py +0 -0
  183. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/tests/test_contract_api.py +0 -0
  184. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/tests/test_data.py +0 -0
  185. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/tests/test_design_system.py +0 -0
  186. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/tests/test_events.py +0 -0
  187. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/tests/test_execution_sqlite_store.py +0 -0
  188. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/tests/test_http_runtime.py +0 -0
  189. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/tests/test_human.py +0 -0
  190. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/tests/test_i18n.py +0 -0
  191. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/tests/test_integration.py +0 -0
  192. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/tests/test_mcp.py +0 -0
  193. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/tests/test_mcp_runtime.py +0 -0
  194. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/tests/test_memory.py +0 -0
  195. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/tests/test_mesh.py +0 -0
  196. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/tests/test_mesh_event_bus.py +0 -0
  197. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/tests/test_model.py +0 -0
  198. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/tests/test_object_store.py +0 -0
  199. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/tests/test_persistence.py +0 -0
  200. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/tests/test_planner_adaptive.py +0 -0
  201. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/tests/test_primitives.py +0 -0
  202. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/tests/test_provider_switching.py +0 -0
  203. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/tests/test_providers.py +0 -0
  204. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/tests/test_queue.py +0 -0
  205. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/tests/test_reactive.py +0 -0
  206. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/tests/test_runtime.py +0 -0
  207. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/tests/test_scheduler.py +0 -0
  208. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/tests/test_security.py +0 -0
  209. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/tests/test_seo.py +0 -0
  210. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/tests/test_state.py +0 -0
  211. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/tests/test_theme.py +0 -0
  212. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/tests/test_theme_presets.py +0 -0
  213. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/tests/test_tools.py +0 -0
  214. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/tests/test_ui.py +0 -0
  215. {voodoo_framework-2.0.0 → voodoo_framework-2.1.0}/tests/test_websocket.py +0 -0
  216. {voodoo_framework-2.0.0 → voodoo_framework-2.1.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.0.0
3
+ Version: 2.1.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>
@@ -466,7 +466,7 @@ voodoo auth secret-key
466
466
 
467
467
  ## Project Status & Roadmap
468
468
 
469
- Voodoo is in active development (v1.16.0, Beta). The core runtime, UI system, AI agents, MCP, durable execution, and adaptive runtime are production-ready. PostgreSQL, S3/R2, and Redis adapters are shipped behind optional extras; the AI runtime (model-provider protocol, memory, durable agents, and durable human-in-the-loop) is the current focus.
469
+ Voodoo is in active development (v2.0.0, Beta). The core runtime, UI system, AI agents, MCP, durable execution, adaptive runtime, model-provider protocol, layered memory, and durable agent registry are production-ready. PostgreSQL, S3/R2, and Redis adapters are shipped behind optional extras; durable human-in-the-loop is the current focus.
470
470
 
471
471
  ```mermaid
472
472
  timeline
@@ -477,26 +477,20 @@ timeline
477
477
  Unified Runtime : ExecutionEngine : Planner : Adaptive supervisor
478
478
  Durable Local Runtime : SQLite queue : Executions : Scheduler : Objects : Events
479
479
  Production Providers : PostgreSQL : S3/R2 : Redis
480
- section In Progress (v1.16–v1.19)
480
+ section Shipped (v1.16–v2.0)
481
481
  ModelProvider protocol : Model descriptors : Routing aliases
482
482
  Memory capability : Layered memory : SQLite + FTS5
483
- Durable Agents & HITL : Agent registry : Resumable approvals
484
- section Planned (v2.0+)
485
- Capability Security & Secrets : v2.0
486
- Observability & Protocol : v2.1–v2.2
487
- Local Runtime DX : v2.3
488
- ```
489
-
490
- | Milestone | Version | Status |
491
- |-----------|---------|--------|
492
- | Core framework — runtime, UI, routing, reactive state | v1.0–v1.1 | ✅ Shipped |
493
- | AI & unified runtime — agents, tools, MCP, HITL, ExecutionEngine, planner, adaptive runtime | v1.2 | ✅ Shipped |
494
- | Durable local runtime (zero-infra) — SQLite storage, queue, executions, scheduler, objects, events | v1.3–v1.9 | ✅ Shipped |
495
- | Adapter contracts & runtime configuration | v1.10–v1.11 | ✅ Shipped |
496
- | Production providers (optional) — PostgreSQL, S3/R2, Redis | v1.12–v1.15 | ✅ Shipped |
497
- | AI runtime — model-provider protocol, memory, durable agents, durable HITL | v1.16–v1.19 | 🚧 In Progress |
498
- | Capability security & secrets | v2.0 | 📋 Planned |
499
- | Protocol stability & DX — observability, schemas, local runtime | v2.1–v2.3 | 📋 Planned |
483
+ Durable Agents : Agent registry : v2.0
484
+ section In Progress (v2.1)
485
+ Durable HITL : Resumable approvals
486
+ section Planned (v2.2+)
487
+ Capability Security & Secrets : v2.2
488
+ Observability & Protocol : v2.3–v2.4
489
+ Local Runtime DX : v2.5
490
+ | AI runtime — model-provider protocol, memory, durable agent registry | v1.16–v2.0 | ✅ Shipped |
491
+ | Durable human-in-the-loop — resumable approvals | v2.1 | 🚧 In Progress |
492
+ | Capability security & secrets | v2.2 | 📋 Planned |
493
+ | Protocol stability & DX — observability, schemas, local runtime | v2.3–v2.5 | 📋 Planned |
500
494
 
501
495
  See the [master roadmap](ROADMAP.md) and [sprint plan](SPRINT_PLAN.md) for details.
502
496
 
@@ -403,7 +403,7 @@ voodoo auth secret-key
403
403
 
404
404
  ## Project Status & Roadmap
405
405
 
406
- Voodoo is in active development (v1.16.0, Beta). The core runtime, UI system, AI agents, MCP, durable execution, and adaptive runtime are production-ready. PostgreSQL, S3/R2, and Redis adapters are shipped behind optional extras; the AI runtime (model-provider protocol, memory, durable agents, and durable human-in-the-loop) is the current focus.
406
+ Voodoo is in active development (v2.0.0, Beta). The core runtime, UI system, AI agents, MCP, durable execution, adaptive runtime, model-provider protocol, layered memory, and durable agent registry are production-ready. PostgreSQL, S3/R2, and Redis adapters are shipped behind optional extras; durable human-in-the-loop is the current focus.
407
407
 
408
408
  ```mermaid
409
409
  timeline
@@ -414,26 +414,20 @@ timeline
414
414
  Unified Runtime : ExecutionEngine : Planner : Adaptive supervisor
415
415
  Durable Local Runtime : SQLite queue : Executions : Scheduler : Objects : Events
416
416
  Production Providers : PostgreSQL : S3/R2 : Redis
417
- section In Progress (v1.16–v1.19)
417
+ section Shipped (v1.16–v2.0)
418
418
  ModelProvider protocol : Model descriptors : Routing aliases
419
419
  Memory capability : Layered memory : SQLite + FTS5
420
- Durable Agents & HITL : Agent registry : Resumable approvals
421
- section Planned (v2.0+)
422
- Capability Security & Secrets : v2.0
423
- Observability & Protocol : v2.1–v2.2
424
- Local Runtime DX : v2.3
425
- ```
426
-
427
- | Milestone | Version | Status |
428
- |-----------|---------|--------|
429
- | Core framework — runtime, UI, routing, reactive state | v1.0–v1.1 | ✅ Shipped |
430
- | AI & unified runtime — agents, tools, MCP, HITL, ExecutionEngine, planner, adaptive runtime | v1.2 | ✅ Shipped |
431
- | Durable local runtime (zero-infra) — SQLite storage, queue, executions, scheduler, objects, events | v1.3–v1.9 | ✅ Shipped |
432
- | Adapter contracts & runtime configuration | v1.10–v1.11 | ✅ Shipped |
433
- | Production providers (optional) — PostgreSQL, S3/R2, Redis | v1.12–v1.15 | ✅ Shipped |
434
- | AI runtime — model-provider protocol, memory, durable agents, durable HITL | v1.16–v1.19 | 🚧 In Progress |
435
- | Capability security & secrets | v2.0 | 📋 Planned |
436
- | Protocol stability & DX — observability, schemas, local runtime | v2.1–v2.3 | 📋 Planned |
420
+ Durable Agents : Agent registry : v2.0
421
+ section In Progress (v2.1)
422
+ Durable HITL : Resumable approvals
423
+ section Planned (v2.2+)
424
+ Capability Security & Secrets : v2.2
425
+ Observability & Protocol : v2.3–v2.4
426
+ Local Runtime DX : v2.5
427
+ | AI runtime — model-provider protocol, memory, durable agent registry | v1.16–v2.0 | ✅ Shipped |
428
+ | Durable human-in-the-loop — resumable approvals | v2.1 | 🚧 In Progress |
429
+ | Capability security & secrets | v2.2 | 📋 Planned |
430
+ | Protocol stability & DX — observability, schemas, local runtime | v2.3–v2.5 | 📋 Planned |
437
431
 
438
432
  See the [master roadmap](ROADMAP.md) and [sprint plan](SPRINT_PLAN.md) for details.
439
433
 
@@ -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.0.0"
105
+ __version__ = "2.1.0"
106
106
 
107
107
  __all__ = [
108
108
  # Core runtime
@@ -3,6 +3,7 @@ import typer
3
3
  from voodoo.cli import (
4
4
  agents,
5
5
  ai,
6
+ approvals,
6
7
  dev,
7
8
  doctor,
8
9
  executions,
@@ -38,6 +39,7 @@ app.add_typer(schedules.schedules_app, name="schedules")
38
39
  app.add_typer(executions.executions_app, name="executions")
39
40
  app.add_typer(objects.objects_app, name="objects")
40
41
  app.add_typer(agents.agents_app, name="agents")
42
+ app.add_typer(approvals.approvals_app, name="approvals")
41
43
  app.add_typer(theme.theme_app, name="theme")
42
44
  # Top-level aliases per the Sprint 3 CLI surface: `voodoo execution <id>`
43
45
  # (timeline from the journal) and `voodoo events` (recent journal events).
@@ -0,0 +1,283 @@
1
+ """voodoo approvals — decide pending human approvals from any machine.
2
+
3
+ Sprint 18: human approval is an execution state that survives process
4
+ death. This CLI reads and decides approvals directly from the durable
5
+ execution store (SQLite by default), so a decision can be made on any
6
+ machine — the waiting execution resumes on whichever worker picks it up.
7
+
8
+ voodoo approvals list [--pending] [--json]
9
+ voodoo approvals show <execution-id> [--json]
10
+ voodoo approvals approve <execution-id> [--by NAME] [--note TEXT]
11
+ voodoo approvals deny <execution-id> [--by NAME] [--reason TEXT]
12
+
13
+ With ``--app`` (e.g. ``main:app``) the application module is imported
14
+ first so registered participants (agents, workflows) re-register and the
15
+ approved execution actually re-runs.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import asyncio
21
+ import os
22
+ from pathlib import Path
23
+
24
+ import typer
25
+
26
+ from voodoo.cli import terminal
27
+
28
+ approvals_app = typer.Typer(
29
+ name="approvals",
30
+ help="List, approve, and deny human approvals.",
31
+ no_args_is_help=True,
32
+ rich_markup_mode="rich",
33
+ )
34
+
35
+
36
+ def _resolve_store(store_path: str | None):
37
+ """Return the configured execution store (SQLite by default)."""
38
+ from voodoo.config import config
39
+ from voodoo.runtime.persistence import JSONFileExecutionStore
40
+ from voodoo.storage.execution import SQLiteExecutionStore
41
+
42
+ if store_path is None:
43
+ store_path = os.environ.get("VOODOO_EXECUTION_STORE", config.db_path).replace(
44
+ ":memory:", ".voodoo/state/data.db"
45
+ )
46
+
47
+ if Path(store_path).suffix == ".jsonl":
48
+ return JSONFileExecutionStore(store_path), store_path
49
+ return SQLiteExecutionStore(store_path), store_path
50
+
51
+
52
+ def _load_app(app_str: str | None) -> None:
53
+ """Import the application module so participants re-register."""
54
+ if app_str is None:
55
+ return
56
+ import importlib
57
+ import sys
58
+
59
+ module_name, _, attr = app_str.partition(":")
60
+ attr = attr or "app"
61
+ sys.path.insert(0, os.getcwd())
62
+ mod = importlib.import_module(module_name)
63
+ getattr(mod, attr, None)
64
+
65
+
66
+ def _list_approvals(store, pending_only: bool) -> list[dict]:
67
+ """Read approvals from a store, tolerating stores without the seam."""
68
+ if not hasattr(store, "load_approvals"):
69
+ return []
70
+ return store.load_approvals(pending_only=pending_only)
71
+
72
+
73
+ @approvals_app.command("list")
74
+ def list_approvals(
75
+ pending_only: bool = typer.Option(
76
+ False, "--pending", help="Only show pending approvals"
77
+ ),
78
+ store_path: str = typer.Option(
79
+ None, "--store", help="Path to the execution store (SQLite by default)"
80
+ ),
81
+ app_str: str = typer.Option(
82
+ None, "--app", help="App instance (e.g. main:app) to import first"
83
+ ),
84
+ json_mode: bool = typer.Option(
85
+ False, "--json", help="Machine-readable JSON output"
86
+ ),
87
+ ):
88
+ """List approvals — pending and decided."""
89
+ store, resolved = _resolve_store(store_path)
90
+ _load_app(app_str)
91
+ approvals = _list_approvals(store, pending_only)
92
+
93
+ if json_mode or terminal.is_json_mode():
94
+ terminal.json_output({"store": resolved, "approvals": approvals})
95
+ return
96
+
97
+ terminal.wordmark()
98
+ terminal.blank()
99
+ if not approvals:
100
+ terminal.muted(
101
+ f"no {'pending ' if pending_only else ''}approvals in {resolved}"
102
+ )
103
+ return
104
+ for a in approvals:
105
+ terminal.info(
106
+ f" {str(a.get('id', ''))[:8]} {(a.get('question') or '-')[:60]}"
107
+ )
108
+ terminal.muted(
109
+ f" execution: {str(a.get('execution_id', ''))[:8]}"
110
+ f" status: {a.get('status', '-')}"
111
+ )
112
+ terminal.muted(
113
+ f" requested_by: {a.get('requested_by') or '-'}"
114
+ f" decided_by: {a.get('decided_by') or '-'}"
115
+ )
116
+ terminal.blank()
117
+
118
+
119
+ @approvals_app.command("show")
120
+ def show_approval(
121
+ execution_id: str = typer.Argument(..., help="Execution id of the approval"),
122
+ store_path: str = typer.Option(
123
+ None, "--store", help="Path to the execution store (SQLite by default)"
124
+ ),
125
+ json_mode: bool = typer.Option(
126
+ False, "--json", help="Machine-readable JSON output"
127
+ ),
128
+ ):
129
+ """Show one approval in detail."""
130
+ store, resolved = _resolve_store(store_path)
131
+ approval = (
132
+ store.load_approval(execution_id) if hasattr(store, "load_approval") else None
133
+ )
134
+ if approval is None:
135
+ terminal.error(
136
+ f"approval for execution '{execution_id}' not found in {resolved}"
137
+ )
138
+ raise typer.Exit(1)
139
+
140
+ if json_mode or terminal.is_json_mode():
141
+ terminal.json_output(approval)
142
+ return
143
+
144
+ terminal.wordmark()
145
+ terminal.blank()
146
+ terminal.status_block(
147
+ [
148
+ ("id", str(approval.get("id", ""))),
149
+ ("execution", str(approval.get("execution_id", ""))),
150
+ ("trace", str(approval.get("trace_id", "") or "-")),
151
+ ("status", approval.get("status", "-")),
152
+ ("requested_by", approval.get("requested_by") or "-"),
153
+ ("decided_by", approval.get("decided_by") or "-"),
154
+ ("decided_at", approval.get("decided_at") or "-"),
155
+ ("participant", approval.get("participant") or "-"),
156
+ ]
157
+ )
158
+ question = approval.get("question")
159
+ if question:
160
+ terminal.blank()
161
+ terminal.label_value("question", question)
162
+ reason = approval.get("reason")
163
+ if reason:
164
+ terminal.label_value("reason", reason)
165
+ terminal.blank()
166
+
167
+
168
+ def _decide(
169
+ execution_id: str,
170
+ *,
171
+ decision: str,
172
+ by: str,
173
+ message: str | None,
174
+ store_path: str | None,
175
+ app_str: str | None,
176
+ json_mode: bool,
177
+ ) -> None:
178
+ """Shared approve/deny path: recover waiting executions, then decide."""
179
+ from voodoo.runtime.engine import engine as runtime_engine
180
+
181
+ store, resolved = _resolve_store(store_path)
182
+ runtime_engine.use_store(store)
183
+ # Restore waiting executions + rehydrate persisted approvals so the
184
+ # decision applies to the durable record — not just this process.
185
+ runtime_engine.recover()
186
+ _load_app(app_str)
187
+
188
+ if runtime_engine.approvals.get(execution_id) is None:
189
+ if json_mode or terminal.is_json_mode():
190
+ terminal.json_output(
191
+ {
192
+ "error": "approval not found",
193
+ "execution_id": execution_id,
194
+ "store": resolved,
195
+ }
196
+ )
197
+ else:
198
+ terminal.error(f"approval for execution '{execution_id}' not found")
199
+ raise typer.Exit(1)
200
+
201
+ decide_fn = runtime_engine.approve if decision == "approve" else runtime_engine.deny
202
+ kwargs = {}
203
+ if decision == "approve":
204
+ kwargs["note"] = message
205
+ else:
206
+ kwargs["reason"] = message or "denied"
207
+ executed = asyncio.run(decide_fn(execution_id, by=by, **kwargs))
208
+
209
+ if json_mode or terminal.is_json_mode():
210
+ terminal.json_output(
211
+ {
212
+ "store": resolved,
213
+ "decision": decision,
214
+ "execution_id": execution_id,
215
+ "by": by,
216
+ "resumed": executed.describe() if executed is not None else None,
217
+ }
218
+ )
219
+ return
220
+
221
+ terminal.wordmark()
222
+ terminal.blank()
223
+ icon = "✓" if decision == "approve" else "✗"
224
+ terminal.info(f" {icon} {decision}d execution {execution_id[:8]} by {by}")
225
+ if executed is not None:
226
+ terminal.muted(f" execution → {executed.status.value}")
227
+ else:
228
+ terminal.muted(" execution not found in engine (record updated only)")
229
+ terminal.blank()
230
+
231
+
232
+ @approvals_app.command("approve")
233
+ def approve_cmd(
234
+ execution_id: str = typer.Argument(..., help="Execution id to approve"),
235
+ by: str = typer.Option("human", "--by", help="Who is approving"),
236
+ note: str = typer.Option(None, "--note", help="Optional note"),
237
+ store_path: str = typer.Option(
238
+ None, "--store", help="Path to the execution store (SQLite by default)"
239
+ ),
240
+ app_str: str = typer.Option(
241
+ None, "--app", help="App instance (e.g. main:app) to import first"
242
+ ),
243
+ json_mode: bool = typer.Option(
244
+ False, "--json", help="Machine-readable JSON output"
245
+ ),
246
+ ):
247
+ """Approve a waiting execution — it resumes on a worker."""
248
+ _decide(
249
+ execution_id,
250
+ decision="approve",
251
+ by=by,
252
+ message=note,
253
+ store_path=store_path,
254
+ app_str=app_str,
255
+ json_mode=json_mode,
256
+ )
257
+
258
+
259
+ @approvals_app.command("deny")
260
+ def deny_cmd(
261
+ execution_id: str = typer.Argument(..., help="Execution id to deny"),
262
+ by: str = typer.Option("human", "--by", help="Who is denying"),
263
+ reason: str = typer.Option(None, "--reason", help="Denial reason"),
264
+ store_path: str = typer.Option(
265
+ None, "--store", help="Path to the execution store (SQLite by default)"
266
+ ),
267
+ app_str: str = typer.Option(
268
+ None, "--app", help="App instance (e.g. main:app) to import first"
269
+ ),
270
+ json_mode: bool = typer.Option(
271
+ False, "--json", help="Machine-readable JSON output"
272
+ ),
273
+ ):
274
+ """Deny a waiting execution — it fails with the denial reason."""
275
+ _decide(
276
+ execution_id,
277
+ decision="deny",
278
+ by=by,
279
+ message=reason,
280
+ store_path=store_path,
281
+ app_str=app_str,
282
+ json_mode=json_mode,
283
+ )
@@ -117,6 +117,10 @@ class ExecutionEngine:
117
117
  self.approvals = ApprovalRegistry()
118
118
  self._execution_store: Any = None
119
119
  self._checkpoint_sequences: dict[str, int] = {}
120
+ #: Durable compute registry — named participants that can be
121
+ #: re-resolved after a restart (Sprint 18). Maps participant
122
+ #: name → compute callable + metadata.
123
+ self._participants: dict[str, Any] = {}
120
124
 
121
125
  # -- persistence / recovery ---------------------------------------------
122
126
 
@@ -124,6 +128,35 @@ class ExecutionEngine:
124
128
  """Attach an ExecutionStore (persistence seam, Phase 11)."""
125
129
  self._execution_store = store
126
130
 
131
+ def register_participant(
132
+ self,
133
+ name: str,
134
+ compute: ComputeFn | None = None,
135
+ *,
136
+ execute: Any | None = None,
137
+ kind: str = "compute",
138
+ capabilities: list[str] | None = None,
139
+ ) -> None:
140
+ """Register a named compute participant for durable resume (Sprint 18).
141
+
142
+ Participants registered here can be re-resolved by name after a
143
+ process restart, making ``WAITING_FOR_HUMAN`` executions fully
144
+ resumable on any worker (ROADMAP §50).
145
+ """
146
+ self._participants[name] = {
147
+ "compute": compute,
148
+ "execute": execute,
149
+ "kind": kind,
150
+ "capabilities": capabilities or [],
151
+ }
152
+
153
+ def resolve_participant(self, name: str) -> Any | None:
154
+ """Resolve a registered participant by name.
155
+
156
+ Returns the registration dict, or ``None`` when not registered.
157
+ """
158
+ return self._participants.get(name)
159
+
127
160
  def _persist(self, execution: Execution) -> None:
128
161
  """Checkpoint an execution — raises on failure (spec §51.16)."""
129
162
  if self._execution_store is None:
@@ -253,6 +286,7 @@ class ExecutionEngine:
253
286
  else None
254
287
  ),
255
288
  reason=record["reason"],
289
+ participant=record.get("participant"),
256
290
  )
257
291
  self.approvals.records[execution.id] = approval
258
292
 
@@ -273,7 +307,7 @@ class ExecutionEngine:
273
307
  self._persist_approval(approval)
274
308
  self._journal_approval_decision(
275
309
  execution_id,
276
- "human.approved",
310
+ "approval.granted",
277
311
  {"by": by, "capability": approval.capability},
278
312
  )
279
313
  waiting = self.executions.get(execution_id)
@@ -283,6 +317,16 @@ class ExecutionEngine:
283
317
  "human.approved",
284
318
  {"execution_id": execution_id, "by": by, "capability": approval.capability},
285
319
  )
320
+
321
+ # Durable resume (Sprint 18): when the live compute is gone (e.g.
322
+ # after a restart) but a registered participant exists, re-resolve
323
+ # the compute from the participant registry. The waiting execution's
324
+ # persisted intent supplies the outcome when the approval record
325
+ # has none (a restarted process serializes no live objects).
326
+ if approval.compute is None and approval.participant is not None:
327
+ approval.compute = self._participant_compute(approval)
328
+ if approval.intent is None and waiting is not None:
329
+ approval.intent = waiting.intent
286
330
  if approval.compute is None or approval.intent is None:
287
331
  if waiting is not None:
288
332
  waiting.complete(result={"approved": True, "by": by})
@@ -310,6 +354,15 @@ class ExecutionEngine:
310
354
  waiting.complete(result=resumed.result)
311
355
  return resumed
312
356
 
357
+ def _participant_compute(self, approval: Any) -> ComputeFn | None:
358
+ """Synchronously re-resolve compute from the participant registry."""
359
+ if approval.participant is None:
360
+ return None
361
+ participant = self.resolve_participant(approval.participant)
362
+ if participant is None:
363
+ return None
364
+ return participant["compute"]
365
+
313
366
  async def deny(
314
367
  self, execution_id: str, *, by: str = "human", reason: str = "denied"
315
368
  ) -> Execution | None:
@@ -324,7 +377,7 @@ class ExecutionEngine:
324
377
  self._persist_approval(approval)
325
378
  self._journal_approval_decision(
326
379
  execution_id,
327
- "human.denied",
380
+ "approval.denied",
328
381
  {"by": by, "reason": reason},
329
382
  )
330
383
  waiting = self.executions.get(execution_id)
@@ -526,6 +579,15 @@ class ExecutionEngine:
526
579
  # Persist the pending approval so a restart can rehydrate it
527
580
  # (spec §30 — decisions recorded as journal events on decide).
528
581
  self._persist_approval(approval)
582
+ self._journal_approval_decision(
583
+ execution.id,
584
+ "approval.requested",
585
+ {
586
+ "capability": exc.context.get("capability"),
587
+ "question": exc.message,
588
+ "requested_by": execution.actor,
589
+ },
590
+ )
529
591
  await self._emit(
530
592
  "human.approval_required",
531
593
  {
@@ -54,6 +54,12 @@ class Approval:
54
54
  id: str = field(default_factory=lambda: str(uuid4()))
55
55
  created_at: datetime = field(default_factory=lambda: datetime.now(UTC))
56
56
 
57
+ #: Registered participant name — the durable handle to what re-runs
58
+ #: once approved (resolved through the engine's planner registry;
59
+ #: Sprint 18). Takes precedence over the live ``compute`` callable
60
+ #: when resuming after a restart.
61
+ participant: str | None = None
62
+
57
63
  # resumable payload — what to re-run once approved
58
64
  intent: Intent | None = None
59
65
  compute: ComputeFn | None = None
@@ -96,6 +102,7 @@ class ApprovalRegistry:
96
102
  compute: ComputeFn | None = None,
97
103
  output_type: type | None = None,
98
104
  context: ExecutionContext | None = None,
105
+ participant: str | None = None,
99
106
  ) -> Approval:
100
107
  approval = Approval(
101
108
  execution_id=execution.id,
@@ -112,6 +119,7 @@ class ApprovalRegistry:
112
119
  compute=compute,
113
120
  output_type=output_type,
114
121
  context=context,
122
+ participant=participant,
115
123
  )
116
124
  self.records[execution.id] = approval
117
125
  return approval
@@ -15,7 +15,8 @@ from voodoo.storage.database.interfaces import Migration
15
15
  from voodoo.storage.database.sqlite import register_framework_migration
16
16
 
17
17
  # Versions follow the global framework namespace: … 6 events, 7 artifacts,
18
- # 8 approvals.
18
+ # 8 approvals (Sprint 18 added the `participant` column to the v8 CREATE —
19
+ # existing databases add it via a guarded ALTER in each store's setup).
19
20
  EXECUTION_ARTIFACTS_MIGRATION_VERSION = 7
20
21
  EXECUTION_APPROVALS_MIGRATION_VERSION = 8
21
22
 
@@ -47,6 +48,7 @@ EXECUTION_APPROVALS_MIGRATION = Migration(
47
48
  name="execution_approvals",
48
49
  statements=(
49
50
  # Approvals (Sprint 4) — pending human decisions survive restarts.
51
+ # `participant` (Sprint 18) — durable compute handle for resume.
50
52
  """
51
53
  CREATE TABLE IF NOT EXISTS approvals (
52
54
  id TEXT PRIMARY KEY,
@@ -59,7 +61,8 @@ EXECUTION_APPROVALS_MIGRATION = Migration(
59
61
  decided_by TEXT,
60
62
  decided_at TEXT,
61
63
  reason TEXT,
62
- created_at TEXT NOT NULL
64
+ created_at TEXT NOT NULL,
65
+ participant TEXT
63
66
  )
64
67
  """,
65
68
  "CREATE INDEX IF NOT EXISTS idx_approvals_execution "
@@ -81,6 +81,17 @@ class PostgresExecutionStore:
81
81
  cur.execute(
82
82
  _translate("ALTER TABLE executions ADD COLUMN checkpoint TEXT")
83
83
  )
84
+ # Sprint 18: durable participant column for approvals — guarded
85
+ # because databases created at v8 already lack it.
86
+ with self._conn.cursor() as cur:
87
+ cur.execute(
88
+ "SELECT column_name FROM information_schema.columns "
89
+ "WHERE table_name = 'approvals' AND column_name = 'participant'"
90
+ )
91
+ if cur.fetchone() is None:
92
+ cur.execute(
93
+ _translate("ALTER TABLE approvals ADD COLUMN participant TEXT")
94
+ )
84
95
  self._conn.commit()
85
96
 
86
97
  # -- ExecutionStore protocol (sync) ------------------------------------
@@ -230,13 +241,14 @@ class PostgresExecutionStore:
230
241
  _translate(
231
242
  "INSERT INTO approvals ("
232
243
  "id, execution_id, trace_id, capability, question, requested_by, "
233
- "status, decided_by, decided_at, reason, created_at"
234
- ") VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) "
244
+ "status, decided_by, decided_at, reason, created_at, participant"
245
+ ") VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) "
235
246
  "ON CONFLICT (id) DO UPDATE SET "
236
247
  "status = excluded.status, "
237
248
  "decided_by = excluded.decided_by, "
238
249
  "decided_at = excluded.decided_at, "
239
- "reason = excluded.reason"
250
+ "reason = excluded.reason, "
251
+ "participant = excluded.participant"
240
252
  ),
241
253
  (
242
254
  approval.id,
@@ -252,6 +264,7 @@ class PostgresExecutionStore:
252
264
  approval.created_at.isoformat()
253
265
  if approval.created_at
254
266
  else datetime.now(UTC).isoformat(),
267
+ getattr(approval, "participant", None),
255
268
  ),
256
269
  )
257
270
  self._conn.commit()
@@ -268,6 +281,17 @@ class PostgresExecutionStore:
268
281
  row = cur.fetchone()
269
282
  return dict(row) if row is not None else None
270
283
 
284
+ def load_approvals(self, pending_only: bool = False) -> list[dict[str, Any]]:
285
+ """List approvals — newest first, optionally pending only (Sprint 18)."""
286
+ query = "SELECT * FROM approvals"
287
+ if pending_only:
288
+ query += " WHERE status = 'pending'"
289
+ query += " ORDER BY created_at DESC"
290
+ with self._lock, self._conn.cursor() as cur:
291
+ cur.execute(_translate(query))
292
+ rows = cur.fetchall()
293
+ return [dict(row) for row in rows]
294
+
271
295
  # -- internals ---------------------------------------------------------
272
296
 
273
297
  def _upsert_materialized(self, execution: Execution) -> None: