voodoo-framework 2.0.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.
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/PKG-INFO +14 -20
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/README.md +13 -19
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/__init__.py +13 -1
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/cli/__init__.py +2 -0
- voodoo_framework-2.2.0/src/voodoo/cli/approvals.py +283 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/primitives/effect.py +9 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/runtime/capability.py +54 -4
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/runtime/engine.py +87 -6
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/runtime/human.py +8 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/security/__init__.py +26 -0
- voodoo_framework-2.2.0/src/voodoo/security/redaction.py +175 -0
- voodoo_framework-2.2.0/src/voodoo/security/secrets.py +223 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/storage/execution/migrations.py +5 -2
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/storage/execution/postgres.py +27 -3
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/storage/execution/sqlite.py +25 -3
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo_framework.egg-info/PKG-INFO +14 -20
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo_framework.egg-info/SOURCES.txt +5 -0
- voodoo_framework-2.2.0/tests/test_capability_security.py +426 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/tests/test_contract_api.py +11 -0
- voodoo_framework-2.2.0/tests/test_durable_hitl.py +346 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/LICENSE +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/pyproject.toml +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/setup.cfg +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/adapters/__init__.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/adapters/capabilities.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/adapters/registry.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/adapters/tailwind.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/adapters/voodoo_css.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/agent.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/agents/__init__.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/agents/models.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/agents/registry.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/ai/__init__.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/ai/agent.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/ai/providers/__init__.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/ai/providers/anthropic.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/ai/providers/gemini.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/ai/providers/mock.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/ai/providers/ollama.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/ai/providers/openai.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/ai/tools/__init__.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/ai/tools/registry.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/api.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/auth/__init__.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/auth/cookies.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/auth/guards.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/auth/jwt.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/auth/middleware.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/auth/passwords.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/auth/user.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/cli/__main__.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/cli/agents.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/cli/ai.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/cli/auth.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/cli/dev.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/cli/doctor.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/cli/executions.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/cli/generate.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/cli/inspect.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/cli/new.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/cli/objects.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/cli/recover.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/cli/routes.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/cli/scaffolding.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/cli/schedules.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/cli/tasks.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/cli/terminal.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/cli/theme.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/cli/version.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/components.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/config.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/core/__init__.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/core/app.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/core/errors.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/core/events.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/core/render.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/core/routing.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/core/sitemap.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/core/state.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/data/__init__.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/data/base.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/data/model.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/i18n.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/mcp/__init__.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/mcp/client.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/memory/__init__.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/memory/interfaces.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/memory/sqlite.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/mesh/__init__.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/mesh/client.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/mesh/event_bus.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/primitives/__init__.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/primitives/capability.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/primitives/compute.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/primitives/constraint.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/primitives/intent.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/primitives/resource.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/primitives/state.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/primitives/time.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/py.typed +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/queue.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/routing/api.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/routing/pages.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/runtime/__init__.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/runtime/adaptive.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/runtime/constraint.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/runtime/context.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/runtime/errors.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/runtime/execution.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/runtime/graph.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/runtime/persistence.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/runtime/planner.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/runtime/scheduler.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/runtime/task.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/runtime/workflow.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/schedule.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/security/cors.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/security/csrf.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/security/headers.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/security/passwords.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/security/rate_limit.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/seo.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/static/client.js +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/status.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/storage/__init__.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/storage/cache/__init__.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/storage/cache/interfaces.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/storage/cache/memory.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/storage/cache/redis.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/storage/database/__init__.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/storage/database/interfaces.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/storage/database/postgres.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/storage/database/sqlite.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/storage/events/__init__.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/storage/events/interfaces.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/storage/events/local.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/storage/events/postgres.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/storage/events/sqlite.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/storage/execution/__init__.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/storage/manager.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/storage/objects/__init__.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/storage/objects/interfaces.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/storage/objects/local.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/storage/objects/s3.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/storage/queue/__init__.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/storage/queue/interfaces.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/storage/queue/memory.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/storage/queue/postgres.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/storage/queue/redis.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/storage/queue/sqlite.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/storage/scheduler/__init__.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/storage/scheduler/sqlite.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/telemetry/__init__.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/telemetry/api.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/telemetry/middleware.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/telemetry/store.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/theme.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/tools/__init__.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/tools/registry.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/ui/__init__.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/ui/component.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/ui/events.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/ui/library.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/ui/markdown.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/ui/rendering.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/ui/state.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/ui/styles/__init__.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/ui/styles/presets.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/ui/styles/theme.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/workers/__init__.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo/workers/queue.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo_framework.egg-info/dependency_links.txt +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo_framework.egg-info/entry_points.txt +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo_framework.egg-info/requires.txt +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/src/voodoo_framework.egg-info/top_level.txt +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/tests/test_agent.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/tests/test_agent_registry.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/tests/test_agent_runtime.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/tests/test_app.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/tests/test_auth.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/tests/test_checkpoint_resume.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/tests/test_chrome.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/tests/test_cli.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/tests/test_cli_inspect.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/tests/test_components.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/tests/test_config.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/tests/test_data.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/tests/test_design_system.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/tests/test_events.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/tests/test_execution_sqlite_store.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/tests/test_http_runtime.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/tests/test_human.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/tests/test_i18n.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/tests/test_integration.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/tests/test_mcp.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/tests/test_mcp_runtime.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/tests/test_memory.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/tests/test_mesh.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/tests/test_mesh_event_bus.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/tests/test_model.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/tests/test_object_store.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/tests/test_persistence.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/tests/test_planner_adaptive.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/tests/test_primitives.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/tests/test_provider_switching.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/tests/test_providers.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/tests/test_queue.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/tests/test_reactive.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/tests/test_runtime.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/tests/test_scheduler.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/tests/test_security.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/tests/test_seo.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/tests/test_state.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/tests/test_theme.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/tests/test_theme_presets.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/tests/test_tools.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/tests/test_ui.py +0 -0
- {voodoo_framework-2.0.0 → voodoo_framework-2.2.0}/tests/test_websocket.py +0 -0
- {voodoo_framework-2.0.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.
|
|
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>
|
|
@@ -466,7 +466,7 @@ voodoo auth secret-key
|
|
|
466
466
|
|
|
467
467
|
## Project Status & Roadmap
|
|
468
468
|
|
|
469
|
-
Voodoo is in active development (
|
|
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
|
|
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
|
|
484
|
-
section
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
|
491
|
-
|
|
492
|
-
|
|
|
493
|
-
|
|
|
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 (
|
|
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
|
|
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
|
|
421
|
-
section
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
|
428
|
-
|
|
429
|
-
|
|
|
430
|
-
|
|
|
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.
|
|
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",
|
|
@@ -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
|
+
)
|
|
@@ -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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
}
|