voodoo-framework 2.1.0__tar.gz → 2.3.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.1.0 → voodoo_framework-2.3.0}/PKG-INFO +5 -1
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/pyproject.toml +5 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/__init__.py +13 -1
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/cli/__init__.py +4 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/cli/doctor.py +48 -0
- voodoo_framework-2.3.0/src/voodoo/cli/status.py +73 -0
- voodoo_framework-2.3.0/src/voodoo/cli/workers_cmd.py +71 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/primitives/effect.py +9 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/runtime/capability.py +54 -4
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/runtime/engine.py +23 -4
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/security/__init__.py +26 -0
- voodoo_framework-2.3.0/src/voodoo/security/redaction.py +175 -0
- voodoo_framework-2.3.0/src/voodoo/security/secrets.py +223 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/telemetry/__init__.py +4 -0
- voodoo_framework-2.3.0/src/voodoo/telemetry/otlp.py +108 -0
- voodoo_framework-2.3.0/src/voodoo/telemetry/store.py +355 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/workers/queue.py +9 -2
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo_framework.egg-info/PKG-INFO +5 -1
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo_framework.egg-info/SOURCES.txt +7 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo_framework.egg-info/requires.txt +5 -0
- voodoo_framework-2.3.0/tests/test_capability_security.py +426 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/tests/test_contract_api.py +11 -0
- voodoo_framework-2.3.0/tests/test_observability.py +459 -0
- voodoo_framework-2.1.0/src/voodoo/telemetry/store.py +0 -200
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/LICENSE +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/README.md +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/setup.cfg +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/adapters/__init__.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/adapters/capabilities.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/adapters/registry.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/adapters/tailwind.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/adapters/voodoo_css.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/agent.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/agents/__init__.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/agents/models.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/agents/registry.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/ai/__init__.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/ai/agent.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/ai/providers/__init__.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/ai/providers/anthropic.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/ai/providers/gemini.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/ai/providers/mock.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/ai/providers/ollama.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/ai/providers/openai.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/ai/tools/__init__.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/ai/tools/registry.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/api.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/auth/__init__.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/auth/cookies.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/auth/guards.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/auth/jwt.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/auth/middleware.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/auth/passwords.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/auth/user.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/cli/__main__.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/cli/agents.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/cli/ai.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/cli/approvals.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/cli/auth.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/cli/dev.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/cli/executions.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/cli/generate.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/cli/inspect.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/cli/new.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/cli/objects.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/cli/recover.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/cli/routes.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/cli/scaffolding.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/cli/schedules.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/cli/tasks.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/cli/terminal.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/cli/theme.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/cli/version.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/components.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/config.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/core/__init__.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/core/app.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/core/errors.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/core/events.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/core/render.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/core/routing.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/core/sitemap.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/core/state.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/data/__init__.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/data/base.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/data/model.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/i18n.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/mcp/__init__.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/mcp/client.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/memory/__init__.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/memory/interfaces.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/memory/sqlite.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/mesh/__init__.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/mesh/client.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/mesh/event_bus.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/primitives/__init__.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/primitives/capability.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/primitives/compute.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/primitives/constraint.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/primitives/intent.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/primitives/resource.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/primitives/state.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/primitives/time.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/py.typed +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/queue.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/routing/api.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/routing/pages.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/runtime/__init__.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/runtime/adaptive.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/runtime/constraint.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/runtime/context.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/runtime/errors.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/runtime/execution.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/runtime/graph.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/runtime/human.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/runtime/persistence.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/runtime/planner.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/runtime/scheduler.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/runtime/task.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/runtime/workflow.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/schedule.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/security/cors.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/security/csrf.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/security/headers.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/security/passwords.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/security/rate_limit.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/seo.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/static/client.js +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/status.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/storage/__init__.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/storage/cache/__init__.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/storage/cache/interfaces.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/storage/cache/memory.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/storage/cache/redis.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/storage/database/__init__.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/storage/database/interfaces.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/storage/database/postgres.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/storage/database/sqlite.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/storage/events/__init__.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/storage/events/interfaces.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/storage/events/local.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/storage/events/postgres.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/storage/events/sqlite.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/storage/execution/__init__.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/storage/execution/migrations.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/storage/execution/postgres.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/storage/execution/sqlite.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/storage/manager.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/storage/objects/__init__.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/storage/objects/interfaces.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/storage/objects/local.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/storage/objects/s3.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/storage/queue/__init__.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/storage/queue/interfaces.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/storage/queue/memory.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/storage/queue/postgres.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/storage/queue/redis.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/storage/queue/sqlite.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/storage/scheduler/__init__.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/storage/scheduler/sqlite.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/telemetry/api.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/telemetry/middleware.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/theme.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/tools/__init__.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/tools/registry.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/ui/__init__.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/ui/component.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/ui/events.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/ui/library.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/ui/markdown.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/ui/rendering.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/ui/state.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/ui/styles/__init__.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/ui/styles/presets.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/ui/styles/theme.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo/workers/__init__.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo_framework.egg-info/dependency_links.txt +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo_framework.egg-info/entry_points.txt +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/src/voodoo_framework.egg-info/top_level.txt +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/tests/test_agent.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/tests/test_agent_registry.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/tests/test_agent_runtime.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/tests/test_app.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/tests/test_auth.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/tests/test_checkpoint_resume.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/tests/test_chrome.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/tests/test_cli.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/tests/test_cli_inspect.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/tests/test_components.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/tests/test_config.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/tests/test_data.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/tests/test_design_system.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/tests/test_durable_hitl.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/tests/test_events.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/tests/test_execution_sqlite_store.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/tests/test_http_runtime.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/tests/test_human.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/tests/test_i18n.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/tests/test_integration.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/tests/test_mcp.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/tests/test_mcp_runtime.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/tests/test_memory.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/tests/test_mesh.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/tests/test_mesh_event_bus.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/tests/test_model.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/tests/test_object_store.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/tests/test_persistence.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/tests/test_planner_adaptive.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/tests/test_primitives.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/tests/test_provider_switching.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/tests/test_providers.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/tests/test_queue.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/tests/test_reactive.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/tests/test_runtime.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/tests/test_scheduler.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/tests/test_security.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/tests/test_seo.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/tests/test_state.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/tests/test_theme.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/tests/test_theme_presets.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/tests/test_tools.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/tests/test_ui.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.0}/tests/test_websocket.py +0 -0
- {voodoo_framework-2.1.0 → voodoo_framework-2.3.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.3.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>
|
|
@@ -59,6 +59,10 @@ Requires-Dist: boto3>=1.34; extra == "s3"
|
|
|
59
59
|
Requires-Dist: botocore>=1.34; extra == "s3"
|
|
60
60
|
Provides-Extra: redis
|
|
61
61
|
Requires-Dist: redis>=5.0; extra == "redis"
|
|
62
|
+
Provides-Extra: otel
|
|
63
|
+
Requires-Dist: opentelemetry-api>=1.20; extra == "otel"
|
|
64
|
+
Requires-Dist: opentelemetry-sdk>=1.20; extra == "otel"
|
|
65
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-grpc>=1.20; extra == "otel"
|
|
62
66
|
Dynamic: license-file
|
|
63
67
|
|
|
64
68
|
<picture>
|
|
@@ -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.3.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",
|
|
@@ -13,9 +13,11 @@ from voodoo.cli import (
|
|
|
13
13
|
recover,
|
|
14
14
|
routes,
|
|
15
15
|
schedules,
|
|
16
|
+
status,
|
|
16
17
|
tasks,
|
|
17
18
|
theme,
|
|
18
19
|
version,
|
|
20
|
+
workers_cmd,
|
|
19
21
|
)
|
|
20
22
|
from voodoo.cli.auth import auth_app
|
|
21
23
|
from voodoo.cli.inspect import inspect_app
|
|
@@ -45,6 +47,8 @@ app.add_typer(theme.theme_app, name="theme")
|
|
|
45
47
|
# (timeline from the journal) and `voodoo events` (recent journal events).
|
|
46
48
|
app.command("execution")(executions.show_execution)
|
|
47
49
|
app.command("events")(executions.list_events)
|
|
50
|
+
app.command()(status.status)
|
|
51
|
+
app.command()(workers_cmd.workers)
|
|
48
52
|
app.command()(version.version)
|
|
49
53
|
app.command()(doctor.doctor)
|
|
50
54
|
app.command()(routes.routes)
|
|
@@ -217,6 +217,54 @@ def doctor(): # noqa: C901
|
|
|
217
217
|
except Exception: # noqa: BLE001 - diagnostics must never crash doctor
|
|
218
218
|
terminal.status("capability matrix", "unavailable")
|
|
219
219
|
|
|
220
|
+
# ── Queue depth ─────────────────────────────────
|
|
221
|
+
terminal.heading("queue")
|
|
222
|
+
try:
|
|
223
|
+
import asyncio
|
|
224
|
+
|
|
225
|
+
async def _check_queue() -> tuple[int, int]:
|
|
226
|
+
from voodoo.workers.queue import _get_queue, _workers
|
|
227
|
+
|
|
228
|
+
depth = 0
|
|
229
|
+
try:
|
|
230
|
+
q = await _get_queue()
|
|
231
|
+
if hasattr(q, "list"):
|
|
232
|
+
tasks = await q.list()
|
|
233
|
+
depth = len(tasks)
|
|
234
|
+
elif hasattr(q, "depth"):
|
|
235
|
+
depth = await q.depth()
|
|
236
|
+
except Exception:
|
|
237
|
+
pass
|
|
238
|
+
return depth, len(_workers)
|
|
239
|
+
|
|
240
|
+
depth, n_workers = asyncio.run(_check_queue())
|
|
241
|
+
terminal.status("depth", str(depth))
|
|
242
|
+
terminal.status("registered workers", str(n_workers))
|
|
243
|
+
except Exception: # noqa: BLE001
|
|
244
|
+
terminal.status("queue", "unavailable")
|
|
245
|
+
|
|
246
|
+
# ── Schedules ───────────────────────────────────
|
|
247
|
+
terminal.heading("schedules")
|
|
248
|
+
try:
|
|
249
|
+
from pathlib import Path as _Path
|
|
250
|
+
|
|
251
|
+
sched_db = _Path(cfg.db_path).parent / "schedules.db"
|
|
252
|
+
if sched_db.exists():
|
|
253
|
+
terminal.status("scheduler db", "present")
|
|
254
|
+
else:
|
|
255
|
+
terminal.status("scheduler db", "not found")
|
|
256
|
+
except Exception: # noqa: BLE001
|
|
257
|
+
terminal.status("schedules", "unavailable")
|
|
258
|
+
|
|
259
|
+
# ── OTLP ────────────────────────────────────────
|
|
260
|
+
terminal.heading("otel")
|
|
261
|
+
try:
|
|
262
|
+
from voodoo.telemetry.otlp import is_available
|
|
263
|
+
|
|
264
|
+
terminal.status("otlp exporter", "active" if is_available() else "off")
|
|
265
|
+
except Exception: # noqa: BLE001
|
|
266
|
+
terminal.status("otlp exporter", "off")
|
|
267
|
+
|
|
220
268
|
# ── AI Kit ──────────────────────────────────────
|
|
221
269
|
terminal.heading("ai kit")
|
|
222
270
|
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""``voodoo status`` — runtime health overview.
|
|
2
|
+
|
|
3
|
+
Displays a quick snapshot of the runtime: request counts, error rate,
|
|
4
|
+
latency, agent activity, queue depth, and OTel export status.
|
|
5
|
+
Works after a restart because rolling counters are persisted to
|
|
6
|
+
``.voodoo/telemetry_summary.json``.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import typer
|
|
12
|
+
from rich.console import Console
|
|
13
|
+
from rich.table import Table
|
|
14
|
+
|
|
15
|
+
__all__ = ["status"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def status() -> None:
|
|
19
|
+
"""Show runtime health overview."""
|
|
20
|
+
from voodoo.telemetry import telemetry_store
|
|
21
|
+
from voodoo.telemetry.otlp import is_available as otlp_available
|
|
22
|
+
|
|
23
|
+
summary = telemetry_store.get_summary()
|
|
24
|
+
console = Console()
|
|
25
|
+
|
|
26
|
+
# -- header --------------------------------------------------------
|
|
27
|
+
console.print("[bold cyan]Voodoo Runtime Status[/bold cyan]")
|
|
28
|
+
console.print()
|
|
29
|
+
|
|
30
|
+
# -- requests & errors ---------------------------------------------
|
|
31
|
+
tbl = Table(show_header=True, header_style="bold")
|
|
32
|
+
tbl.add_column("Metric", style="dim")
|
|
33
|
+
tbl.add_column("Value", justify="right")
|
|
34
|
+
|
|
35
|
+
total = summary.get("requests_total", 0)
|
|
36
|
+
errors = summary.get("errors_total", 0)
|
|
37
|
+
error_rate = f"{(errors / total * 100):.1f}%" if total else "—"
|
|
38
|
+
avg_lat = summary.get("average_latency_ms", 0.0)
|
|
39
|
+
|
|
40
|
+
tbl.add_row("Requests total", str(total))
|
|
41
|
+
tbl.add_row("Errors total", str(errors))
|
|
42
|
+
tbl.add_row("Error rate", error_rate)
|
|
43
|
+
tbl.add_row("Avg latency", f"{avg_lat:.1f} ms")
|
|
44
|
+
tbl.add_row("DB queries", str(summary.get("db_queries", 0)))
|
|
45
|
+
console.print(tbl)
|
|
46
|
+
console.print()
|
|
47
|
+
|
|
48
|
+
# -- agent activity ------------------------------------------------
|
|
49
|
+
agent_tbl = Table(show_header=True, header_style="bold")
|
|
50
|
+
agent_tbl.add_column("Agent Metric", style="dim")
|
|
51
|
+
agent_tbl.add_column("Value", justify="right")
|
|
52
|
+
|
|
53
|
+
agent_tbl.add_row("Agent runs", str(summary.get("agent_runs", 0)))
|
|
54
|
+
agent_tbl.add_row("Tokens in", str(summary.get("agent_tokens_in", 0)))
|
|
55
|
+
agent_tbl.add_row("Tokens out", str(summary.get("agent_tokens_out", 0)))
|
|
56
|
+
agent_tbl.add_row("Agent cost", f"${summary.get('agent_cost', 0.0):.6f}")
|
|
57
|
+
agent_tbl.add_row("Tool calls", str(summary.get("tool_calls_total", 0)))
|
|
58
|
+
agent_tbl.add_row("Tool errors", str(summary.get("tool_errors", 0)))
|
|
59
|
+
agent_tbl.add_row("Spans recorded", str(summary.get("spans_total", 0)))
|
|
60
|
+
console.print(agent_tbl)
|
|
61
|
+
console.print()
|
|
62
|
+
|
|
63
|
+
# -- infrastructure ------------------------------------------------
|
|
64
|
+
infra_tbl = Table(show_header=True, header_style="bold")
|
|
65
|
+
infra_tbl.add_column("Infrastructure", style="dim")
|
|
66
|
+
infra_tbl.add_column("Status")
|
|
67
|
+
|
|
68
|
+
infra_tbl.add_row("OTLP export", "active" if otlp_available() else "off")
|
|
69
|
+
console.print(infra_tbl)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
app = typer.Typer(name="status", help="Runtime health overview.", no_args_is_help=True)
|
|
73
|
+
app.command()(status)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""``voodoo workers`` — worker state & queue depth.
|
|
2
|
+
|
|
3
|
+
Shows registered background workers, their queue depth, and whether
|
|
4
|
+
the worker loop is running.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
import typer
|
|
13
|
+
from rich.console import Console
|
|
14
|
+
from rich.table import Table
|
|
15
|
+
|
|
16
|
+
__all__ = ["workers"]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
async def _gather_worker_info() -> dict[str, Any]:
|
|
20
|
+
"""Collect worker and queue state without side-effects."""
|
|
21
|
+
from voodoo.workers.queue import _get_queue, _worker_tasks, _workers
|
|
22
|
+
|
|
23
|
+
info: dict[str, Any] = {
|
|
24
|
+
"registered": list(_workers.keys()),
|
|
25
|
+
"running_tasks": len([t for t in _worker_tasks if not t.done()]),
|
|
26
|
+
"queue_depth": 0,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
q = await _get_queue()
|
|
31
|
+
if hasattr(q, "list"):
|
|
32
|
+
tasks = await q.list()
|
|
33
|
+
info["queue_depth"] = len(tasks)
|
|
34
|
+
elif hasattr(q, "depth"):
|
|
35
|
+
info["queue_depth"] = await q.depth()
|
|
36
|
+
except Exception:
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
return info
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def workers() -> None:
|
|
43
|
+
"""Show registered workers and queue depth."""
|
|
44
|
+
console = Console()
|
|
45
|
+
info = asyncio.run(_gather_worker_info())
|
|
46
|
+
|
|
47
|
+
console.print("[bold cyan]Voodoo Workers[/bold cyan]")
|
|
48
|
+
console.print()
|
|
49
|
+
|
|
50
|
+
tbl = Table(show_header=True, header_style="bold")
|
|
51
|
+
tbl.add_column("Worker", style="dim")
|
|
52
|
+
tbl.add_column("Status")
|
|
53
|
+
|
|
54
|
+
registered = info["registered"]
|
|
55
|
+
if not registered:
|
|
56
|
+
console.print("[dim]No workers registered.[/dim]")
|
|
57
|
+
else:
|
|
58
|
+
running = info["running_tasks"]
|
|
59
|
+
for name in registered:
|
|
60
|
+
tbl.add_row(name, "running" if running > 0 else "idle")
|
|
61
|
+
console.print(tbl)
|
|
62
|
+
|
|
63
|
+
console.print()
|
|
64
|
+
console.print(f"Queue depth: [bold]{info['queue_depth']}[/bold]")
|
|
65
|
+
console.print(f"Running worker tasks: [bold]{info['running_tasks']}[/bold]")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
app = typer.Typer(
|
|
69
|
+
name="workers", help="Worker state & queue depth.", no_args_is_help=True
|
|
70
|
+
)
|
|
71
|
+
app.command()(workers)
|
|
@@ -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
|
}
|
|
@@ -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
|
-
|
|
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)
|