enya-agent 0.1.0
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.
- package/.env.example +20 -0
- package/.github/workflows/ci.yml +70 -0
- package/.github/workflows/publish.yml +250 -0
- package/.gitmodules +3 -0
- package/Cargo.lock +3584 -0
- package/Cargo.toml +97 -0
- package/crates/enact/Cargo.toml +27 -0
- package/crates/enact/src/lib.rs +60 -0
- package/crates/enact-a2a/Cargo.toml +25 -0
- package/crates/enact-a2a/src/lib.rs +411 -0
- package/crates/enact-channels/Cargo.toml +64 -0
- package/crates/enact-channels/examples/README.md +80 -0
- package/crates/enact-channels/examples/channel_bot.rs +169 -0
- package/crates/enact-channels/examples/telegram-echo.rs +34 -0
- package/crates/enact-channels/examples/whatsapp-echo.rs +142 -0
- package/crates/enact-channels/src/config.rs +213 -0
- package/crates/enact-channels/src/lib.rs +25 -0
- package/crates/enact-channels/src/runtime.rs +237 -0
- package/crates/enact-channels/src/security/mod.rs +5 -0
- package/crates/enact-channels/src/security/pairing.rs +205 -0
- package/crates/enact-channels/src/teams.rs +601 -0
- package/crates/enact-channels/src/telegram.rs +2833 -0
- package/crates/enact-channels/src/traits.rs +200 -0
- package/crates/enact-channels/src/webhook.rs +262 -0
- package/crates/enact-channels/src/whatsapp.rs +310 -0
- package/crates/enact-cli/Cargo.toml +40 -0
- package/crates/enact-cli/src/commands/doctor.rs +62 -0
- package/crates/enact-cli/src/commands/mod.rs +3 -0
- package/crates/enact-cli/src/commands/run.rs +69 -0
- package/crates/enact-cli/src/commands/serve.rs +81 -0
- package/crates/enact-cli/src/config.rs +2 -0
- package/crates/enact-cli/src/main.rs +79 -0
- package/crates/enact-config/Cargo.toml +36 -0
- package/crates/enact-config/ENV_VAR_MAPPING.md +135 -0
- package/crates/enact-config/QUICK_REFERENCE.md +92 -0
- package/crates/enact-config/README.md +107 -0
- package/crates/enact-config/TESTING.md +161 -0
- package/crates/enact-config/examples/test-env-vars.rs +100 -0
- package/crates/enact-config/src/config.rs +399 -0
- package/crates/enact-config/src/encrypted_store.rs +211 -0
- package/crates/enact-config/src/lib.rs +298 -0
- package/crates/enact-config/src/secrets.rs +149 -0
- package/crates/enact-config/src/sync.rs +260 -0
- package/crates/enact-config/test-env-vars.sh +34 -0
- package/crates/enact-config/tests/README.md +99 -0
- package/crates/enact-config/tests/config_integration_test.rs +202 -0
- package/crates/enact-config/tests/security_test.rs +140 -0
- package/crates/enact-context/Cargo.toml +41 -0
- package/crates/enact-context/src/budget.rs +314 -0
- package/crates/enact-context/src/calibrator.rs +535 -0
- package/crates/enact-context/src/compactor.rs +392 -0
- package/crates/enact-context/src/condenser.rs +826 -0
- package/crates/enact-context/src/lib.rs +94 -0
- package/crates/enact-context/src/segment.rs +238 -0
- package/crates/enact-context/src/step_context.rs +645 -0
- package/crates/enact-context/src/token_counter.rs +148 -0
- package/crates/enact-context/src/window.rs +372 -0
- package/crates/enact-core/Cargo.toml +42 -0
- package/crates/enact-core/README.md +98 -0
- package/crates/enact-core/src/background/executor.rs +524 -0
- package/crates/enact-core/src/background/mod.rs +48 -0
- package/crates/enact-core/src/background/target_binding.rs +390 -0
- package/crates/enact-core/src/background/trigger.rs +511 -0
- package/crates/enact-core/src/callable/callable.rs +152 -0
- package/crates/enact-core/src/callable/composite.rs +817 -0
- package/crates/enact-core/src/callable/graph.rs +104 -0
- package/crates/enact-core/src/callable/llm.rs +211 -0
- package/crates/enact-core/src/callable/mod.rs +64 -0
- package/crates/enact-core/src/callable/registry.rs +206 -0
- package/crates/enact-core/src/context/execution_context.rs +757 -0
- package/crates/enact-core/src/context/invocation.rs +99 -0
- package/crates/enact-core/src/context/mod.rs +50 -0
- package/crates/enact-core/src/context/tenant.rs +175 -0
- package/crates/enact-core/src/context/trace.rs +127 -0
- package/crates/enact-core/src/flow/conditional.rs +293 -0
- package/crates/enact-core/src/flow/mod.rs +43 -0
- package/crates/enact-core/src/flow/parallel.rs +437 -0
- package/crates/enact-core/src/flow/repeat.rs +534 -0
- package/crates/enact-core/src/flow/sequential.rs +248 -0
- package/crates/enact-core/src/graph/checkpoint.rs +79 -0
- package/crates/enact-core/src/graph/checkpoint_store.rs +76 -0
- package/crates/enact-core/src/graph/compiled.rs +189 -0
- package/crates/enact-core/src/graph/edge.rs +59 -0
- package/crates/enact-core/src/graph/graph_schema.rs +218 -0
- package/crates/enact-core/src/graph/loader.rs +155 -0
- package/crates/enact-core/src/graph/mod.rs +18 -0
- package/crates/enact-core/src/graph/node/function.rs +49 -0
- package/crates/enact-core/src/graph/node/mod.rs +48 -0
- package/crates/enact-core/src/graph/schema.rs +62 -0
- package/crates/enact-core/src/inbox/message.rs +405 -0
- package/crates/enact-core/src/inbox/mod.rs +31 -0
- package/crates/enact-core/src/inbox/store.rs +355 -0
- package/crates/enact-core/src/kernel/artifact/filesystem.rs +546 -0
- package/crates/enact-core/src/kernel/artifact/metadata.rs +283 -0
- package/crates/enact-core/src/kernel/artifact/mod.rs +27 -0
- package/crates/enact-core/src/kernel/artifact/store.rs +427 -0
- package/crates/enact-core/src/kernel/enforcement.rs +1315 -0
- package/crates/enact-core/src/kernel/error.rs +1200 -0
- package/crates/enact-core/src/kernel/event.rs +1394 -0
- package/crates/enact-core/src/kernel/execution_model.rs +831 -0
- package/crates/enact-core/src/kernel/execution_state.rs +189 -0
- package/crates/enact-core/src/kernel/execution_strategy.rs +117 -0
- package/crates/enact-core/src/kernel/ids.rs +2086 -0
- package/crates/enact-core/src/kernel/interrupt.rs +125 -0
- package/crates/enact-core/src/kernel/kernel.rs +1283 -0
- package/crates/enact-core/src/kernel/mod.rs +205 -0
- package/crates/enact-core/src/kernel/persistence/event_store.rs +270 -0
- package/crates/enact-core/src/kernel/persistence/message_store.rs +908 -0
- package/crates/enact-core/src/kernel/persistence/mod.rs +102 -0
- package/crates/enact-core/src/kernel/persistence/state_store.rs +228 -0
- package/crates/enact-core/src/kernel/persistence/vector_store.rs +299 -0
- package/crates/enact-core/src/kernel/reducer.rs +808 -0
- package/crates/enact-core/src/kernel/replay.rs +153 -0
- package/crates/enact-core/src/lib.rs +413 -0
- package/crates/enact-core/src/memory/episodic.rs +0 -0
- package/crates/enact-core/src/memory/mod.rs +6 -0
- package/crates/enact-core/src/memory/semantic.rs +0 -0
- package/crates/enact-core/src/memory/trait.rs +0 -0
- package/crates/enact-core/src/memory/vector_db.rs +0 -0
- package/crates/enact-core/src/memory/working.rs +0 -0
- package/crates/enact-core/src/policy/execution_policy.rs +292 -0
- package/crates/enact-core/src/policy/filters.rs +458 -0
- package/crates/enact-core/src/policy/input_processor.rs +407 -0
- package/crates/enact-core/src/policy/long_running.rs +134 -0
- package/crates/enact-core/src/policy/mod.rs +193 -0
- package/crates/enact-core/src/policy/pii_input.rs +274 -0
- package/crates/enact-core/src/policy/tenant_policy.rs +453 -0
- package/crates/enact-core/src/policy/tool_policy.rs +407 -0
- package/crates/enact-core/src/providers/mod.rs +63 -0
- package/crates/enact-core/src/providers/trait.rs +292 -0
- package/crates/enact-core/src/runner/callbacks.rs +6 -0
- package/crates/enact-core/src/runner/execution_runner.rs +476 -0
- package/crates/enact-core/src/runner/loop.rs +117 -0
- package/crates/enact-core/src/runner/mod.rs +58 -0
- package/crates/enact-core/src/runner/protected_runner.rs +280 -0
- package/crates/enact-core/src/signal/inmemory.rs +231 -0
- package/crates/enact-core/src/signal/mod.rs +108 -0
- package/crates/enact-core/src/streaming/event_logger.rs +195 -0
- package/crates/enact-core/src/streaming/event_stream.rs +1423 -0
- package/crates/enact-core/src/streaming/mod.rs +108 -0
- package/crates/enact-core/src/streaming/pause_cancel.rs +0 -0
- package/crates/enact-core/src/streaming/protected_emitter.rs +173 -0
- package/crates/enact-core/src/streaming/protection/context.rs +136 -0
- package/crates/enact-core/src/streaming/protection/encryption.rs +289 -0
- package/crates/enact-core/src/streaming/protection/mod.rs +43 -0
- package/crates/enact-core/src/streaming/protection/pii_protection.rs +243 -0
- package/crates/enact-core/src/streaming/protection/processor.rs +166 -0
- package/crates/enact-core/src/streaming/sse.rs +0 -0
- package/crates/enact-core/src/telemetry/exporter.rs +0 -0
- package/crates/enact-core/src/telemetry/init.rs +0 -0
- package/crates/enact-core/src/telemetry/mod.rs +49 -0
- package/crates/enact-core/src/telemetry/spans.rs +245 -0
- package/crates/enact-core/src/tool/agent_tool.rs +177 -0
- package/crates/enact-core/src/tool/browser/mod.rs +0 -0
- package/crates/enact-core/src/tool/browser/webdriver.rs +0 -0
- package/crates/enact-core/src/tool/cost.rs +247 -0
- package/crates/enact-core/src/tool/discovery.rs +0 -0
- package/crates/enact-core/src/tool/dispatcher.rs +347 -0
- package/crates/enact-core/src/tool/filesystem.rs +231 -0
- package/crates/enact-core/src/tool/function.rs +99 -0
- package/crates/enact-core/src/tool/git.rs +162 -0
- package/crates/enact-core/src/tool/http.rs +214 -0
- package/crates/enact-core/src/tool/mcp/client.rs +0 -0
- package/crates/enact-core/src/tool/mcp/mod.rs +0 -0
- package/crates/enact-core/src/tool/mod.rs +51 -0
- package/crates/enact-core/src/tool/reasoning/debugging.rs +0 -0
- package/crates/enact-core/src/tool/reasoning/mcts.rs +0 -0
- package/crates/enact-core/src/tool/reasoning/mod.rs +0 -0
- package/crates/enact-core/src/tool/reasoning/sequential.rs +0 -0
- package/crates/enact-core/src/tool/sandbox/dagger.rs +0 -0
- package/crates/enact-core/src/tool/sandbox/mod.rs +0 -0
- package/crates/enact-core/src/tool/shell.rs +147 -0
- package/crates/enact-core/src/tool/trait.rs +33 -0
- package/crates/enact-core/src/tool/web_search.rs +277 -0
- package/crates/enact-core/src/util/config.rs +0 -0
- package/crates/enact-core/src/util/errors.rs +0 -0
- package/crates/enact-core/src/util/mod.rs +6 -0
- package/crates/enact-core/tests/airgapped_e2e_test.rs +291 -0
- package/crates/enact-core/tests/e2e_agentic_loop.rs +119 -0
- package/crates/enact-core/tests/e2e_test.rs +259 -0
- package/crates/enact-core/tests/graph_test.rs +130 -0
- package/crates/enact-core/tests/stream_event_id_validation.rs +435 -0
- package/crates/enact-cron/Cargo.toml +28 -0
- package/crates/enact-cron/src/lib.rs +44 -0
- package/crates/enact-cron/src/schedule.rs +156 -0
- package/crates/enact-cron/src/store.rs +589 -0
- package/crates/enact-cron/src/types.rs +148 -0
- package/crates/enact-gateway/Cargo.toml +31 -0
- package/crates/enact-gateway/README.md +30 -0
- package/crates/enact-gateway/examples/whatsapp-gateway-runner-mock.rs +59 -0
- package/crates/enact-gateway/examples/whatsapp-gateway.rs +42 -0
- package/crates/enact-gateway/src/lib.rs +582 -0
- package/crates/enact-mcp/Cargo.toml +24 -0
- package/crates/enact-mcp/src/lib.rs +178 -0
- package/crates/enact-memory/Cargo.toml +25 -0
- package/crates/enact-memory/src/backend.rs +20 -0
- package/crates/enact-memory/src/chunker.rs +230 -0
- package/crates/enact-memory/src/embeddings.rs +221 -0
- package/crates/enact-memory/src/lib.rs +67 -0
- package/crates/enact-memory/src/markdown.rs +127 -0
- package/crates/enact-memory/src/none.rs +61 -0
- package/crates/enact-memory/src/sqlite.rs +276 -0
- package/crates/enact-memory/src/traits.rs +65 -0
- package/crates/enact-memory/src/vector.rs +198 -0
- package/crates/enact-oauth/Cargo.toml +27 -0
- package/crates/enact-oauth/src/lib.rs +584 -0
- package/crates/enact-observability/Cargo.toml +22 -0
- package/crates/enact-observability/src/lib.rs +197 -0
- package/crates/enact-providers/Cargo.toml +33 -0
- package/crates/enact-providers/examples/hello-agent.rs +33 -0
- package/crates/enact-providers/src/anthropic.rs +182 -0
- package/crates/enact-providers/src/azure.rs +96 -0
- package/crates/enact-providers/src/bridge.rs +221 -0
- package/crates/enact-providers/src/gemini.rs +227 -0
- package/crates/enact-providers/src/http.rs +78 -0
- package/crates/enact-providers/src/lib.rs +53 -0
- package/crates/enact-providers/src/openai_compatible.rs +167 -0
- package/crates/enact-providers/src/openrouter.rs +33 -0
- package/crates/enact-runner/Cargo.toml +24 -0
- package/crates/enact-runner/README.md +76 -0
- package/crates/enact-runner/src/compaction.rs +225 -0
- package/crates/enact-runner/src/config.rs +118 -0
- package/crates/enact-runner/src/lib.rs +63 -0
- package/crates/enact-runner/src/loop_driver.rs +414 -0
- package/crates/enact-runner/src/parser.rs +421 -0
- package/crates/enact-runner/src/retry.rs +262 -0
- package/crates/enact-runner/tests/integration.rs +278 -0
- package/crates/enact-security/Cargo.toml +22 -0
- package/crates/enact-security/src/audit.rs +375 -0
- package/crates/enact-security/src/lib.rs +37 -0
- package/crates/enact-security/src/policy.rs +406 -0
- package/crates/enact-skills/Cargo.toml +25 -0
- package/crates/enact-skills/src/lib.rs +506 -0
- package/crates/enact-tools/Cargo.toml +22 -0
- package/crates/enact-tools/src/file_read.rs +166 -0
- package/crates/enact-tools/src/file_write.rs +216 -0
- package/crates/enact-tools/src/git_operations.rs +513 -0
- package/crates/enact-tools/src/http_request.rs +417 -0
- package/crates/enact-tools/src/lib.rs +104 -0
- package/crates/enact-tools/src/security.rs +227 -0
- package/crates/enact-tools/src/shell.rs +191 -0
- package/crates/enact-tools/src/traits.rs +159 -0
- package/docs/Makefile +74 -0
- package/docs/config.toml +62 -0
- package/docs/content/_index.md +174 -0
- package/docs/content/a2a/_index.md +431 -0
- package/docs/content/api/_index.md +323 -0
- package/docs/content/channels/_index.md +160 -0
- package/docs/content/channels/teams.md +205 -0
- package/docs/content/channels/telegram.md +182 -0
- package/docs/content/channels/webhook.md +423 -0
- package/docs/content/channels/whatsapp.md +240 -0
- package/docs/content/cli/_index.md +261 -0
- package/docs/content/concepts/_index.md +273 -0
- package/docs/content/configuration/_index.md +241 -0
- package/docs/content/cron/_index.md +248 -0
- package/docs/content/developers/_index.md +278 -0
- package/docs/content/getting-started/_index.md +180 -0
- package/docs/content/installation/_index.md +186 -0
- package/docs/content/installation/uninstall.md +101 -0
- package/docs/content/installation/updating.md +120 -0
- package/docs/content/mcp/_index.md +215 -0
- package/docs/content/memory/_index.md +163 -0
- package/docs/content/oauth/_index.md +515 -0
- package/docs/content/providers/_index.md +206 -0
- package/docs/content/roadmap/_index.md +199 -0
- package/docs/content/security/_index.md +219 -0
- package/docs/content/skills/_index.md +228 -0
- package/docs/content/tools/_index.md +485 -0
- package/docs/content/troubleshooting/_index.md +259 -0
- package/docs/content/yaml-schema/_index.md +294 -0
- package/docs/static/giallo-dark.css +91 -0
- package/docs/static/giallo-light.css +91 -0
- package/docs/themes/tanuki/.github/workflows/deploy.yml +44 -0
- package/docs/themes/tanuki/LICENSE +21 -0
- package/docs/themes/tanuki/README.md +166 -0
- package/docs/themes/tanuki/examples/blog/config.toml +58 -0
- package/docs/themes/tanuki/examples/blog/content/_index.md +4 -0
- package/docs/themes/tanuki/examples/blog/content/about.md +33 -0
- package/docs/themes/tanuki/examples/blog/content/blog/_index.md +7 -0
- package/docs/themes/tanuki/examples/blog/content/blog/api-design-best-practices.md +245 -0
- package/docs/themes/tanuki/examples/blog/content/blog/building-accessible-websites.md +147 -0
- package/docs/themes/tanuki/examples/blog/content/blog/css-grid-vs-flexbox.md +165 -0
- package/docs/themes/tanuki/examples/blog/content/blog/customizing-catppuccin-colors.md +137 -0
- package/docs/themes/tanuki/examples/blog/content/blog/dark-mode-best-practices.md +82 -0
- package/docs/themes/tanuki/examples/blog/content/blog/docker-essentials.md +301 -0
- package/docs/themes/tanuki/examples/blog/content/blog/getting-started-with-zola.md +129 -0
- package/docs/themes/tanuki/examples/blog/content/blog/git-workflow-for-content.md +112 -0
- package/docs/themes/tanuki/examples/blog/content/blog/introduction-to-webassembly.md +183 -0
- package/docs/themes/tanuki/examples/blog/content/blog/modern-javascript-features.md +234 -0
- package/docs/themes/tanuki/examples/blog/content/blog/testing-strategies.md +311 -0
- package/docs/themes/tanuki/examples/blog/content/blog/typography-for-developers.md +104 -0
- package/docs/themes/tanuki/examples/blog/content/blog/welcome-to-tanuki.md +67 -0
- package/docs/themes/tanuki/examples/blog/content/blog/why-static-sites.md +85 -0
- package/docs/themes/tanuki/examples/blog/content/projects.md +64 -0
- package/docs/themes/tanuki/examples/book/config.toml +17 -0
- package/docs/themes/tanuki/examples/book/content/_index.md +12 -0
- package/docs/themes/tanuki/examples/book/content/chapter-1.md +90 -0
- package/docs/themes/tanuki/examples/book/content/chapter-2.md +143 -0
- package/docs/themes/tanuki/examples/book/content/chapter-3.md +217 -0
- package/docs/themes/tanuki/examples/book/content/chapter-4.md +224 -0
- package/docs/themes/tanuki/examples/book/content/chapter-5.md +297 -0
- package/docs/themes/tanuki/examples/book/content/print.md +6 -0
- package/docs/themes/tanuki/examples/docs/config.toml +28 -0
- package/docs/themes/tanuki/examples/docs/content/_index.md +20 -0
- package/docs/themes/tanuki/examples/docs/content/components.md +156 -0
- package/docs/themes/tanuki/examples/docs/content/configuration.md +94 -0
- package/docs/themes/tanuki/examples/docs/content/customization.md +202 -0
- package/docs/themes/tanuki/examples/docs/content/deployment.md +204 -0
- package/docs/themes/tanuki/examples/docs/content/installation.md +59 -0
- package/docs/themes/tanuki/examples/docs/content/print.md +6 -0
- package/docs/themes/tanuki/examples/docs/static/img/tanuki-icon.avif +0 -0
- package/docs/themes/tanuki/examples/index.html +2104 -0
- package/docs/themes/tanuki/mise.toml +108 -0
- package/docs/themes/tanuki/sass/base/_catppuccin.scss +164 -0
- package/docs/themes/tanuki/sass/base/_fonts.scss +64 -0
- package/docs/themes/tanuki/sass/base/_reset.scss +152 -0
- package/docs/themes/tanuki/sass/base/_typography.scss +523 -0
- package/docs/themes/tanuki/sass/components/_buttons.scss +209 -0
- package/docs/themes/tanuki/sass/components/_code.scss +457 -0
- package/docs/themes/tanuki/sass/components/_landing.scss +633 -0
- package/docs/themes/tanuki/sass/components/_layout.scss +294 -0
- package/docs/themes/tanuki/sass/components/_navigation.scss +1200 -0
- package/docs/themes/tanuki/sass/components/_print.scss +237 -0
- package/docs/themes/tanuki/sass/components/_search.scss +224 -0
- package/docs/themes/tanuki/sass/components/_sidebar.scss +473 -0
- package/docs/themes/tanuki/sass/components/_theme-toggle.scss +186 -0
- package/docs/themes/tanuki/sass/modes/_blog.scss +366 -0
- package/docs/themes/tanuki/sass/modes/_product.scss +875 -0
- package/docs/themes/tanuki/sass/modes/_raskell.scss +1696 -0
- package/docs/themes/tanuki/sass/patterns/_buttons.scss +183 -0
- package/docs/themes/tanuki/sass/patterns/_cards.scss +144 -0
- package/docs/themes/tanuki/sass/patterns/_index.scss +9 -0
- package/docs/themes/tanuki/sass/patterns/_lists.scss +259 -0
- package/docs/themes/tanuki/sass/patterns/_sections.scss +243 -0
- package/docs/themes/tanuki/sass/style.scss +47 -0
- package/docs/themes/tanuki/sass/tokens/_colors.scss +139 -0
- package/docs/themes/tanuki/sass/tokens/_spacing.scss +100 -0
- package/docs/themes/tanuki/sass/tokens/_typography.scss +186 -0
- package/docs/themes/tanuki/screenshot.png +0 -0
- package/docs/themes/tanuki/sentinel.kdl +59 -0
- package/docs/themes/tanuki/static/elasticlunr.min.js +10 -0
- package/docs/themes/tanuki/static/fonts/GEIST-LICENSE.txt +92 -0
- package/docs/themes/tanuki/static/fonts/Geist-Variable.woff2 +0 -0
- package/docs/themes/tanuki/static/fonts/GeistMono-Variable.woff2 +0 -0
- package/docs/themes/tanuki/static/img/tanuki-icon.avif +0 -0
- package/docs/themes/tanuki/static/img/tanuki-icon.png +0 -0
- package/docs/themes/tanuki/static/js/anchors.js +18 -0
- package/docs/themes/tanuki/static/js/app.js +274 -0
- package/docs/themes/tanuki/static/js/code.js +394 -0
- package/docs/themes/tanuki/static/js/navigation.js +778 -0
- package/docs/themes/tanuki/static/js/scroll-to-top.js +33 -0
- package/docs/themes/tanuki/static/js/search-raskell.js +240 -0
- package/docs/themes/tanuki/static/js/search.js +215 -0
- package/docs/themes/tanuki/static/js/theme.js +169 -0
- package/docs/themes/tanuki/static/syntax-dark.css +151 -0
- package/docs/themes/tanuki/static/syntax-light.css +151 -0
- package/docs/themes/tanuki/static/wasm/sentinel_playground_wasm.js +486 -0
- package/docs/themes/tanuki/static/wasm/sentinel_playground_wasm_bg.wasm +0 -0
- package/docs/themes/tanuki/templates/404.html +52 -0
- package/docs/themes/tanuki/templates/base.html +428 -0
- package/docs/themes/tanuki/templates/blog.html +66 -0
- package/docs/themes/tanuki/templates/home.html +108 -0
- package/docs/themes/tanuki/templates/index.html +178 -0
- package/docs/themes/tanuki/templates/landing.html +168 -0
- package/docs/themes/tanuki/templates/macros/nav.html +128 -0
- package/docs/themes/tanuki/templates/macros/posts.html +101 -0
- package/docs/themes/tanuki/templates/macros/ui.html +159 -0
- package/docs/themes/tanuki/templates/page.html +135 -0
- package/docs/themes/tanuki/templates/partials/footer.html +38 -0
- package/docs/themes/tanuki/templates/partials/header.html +366 -0
- package/docs/themes/tanuki/templates/partials/nav-buttons.html +55 -0
- package/docs/themes/tanuki/templates/partials/nav-overlay.html +81 -0
- package/docs/themes/tanuki/templates/partials/page-toc-panel.html +43 -0
- package/docs/themes/tanuki/templates/partials/search.html +52 -0
- package/docs/themes/tanuki/templates/partials/sidebar.html +107 -0
- package/docs/themes/tanuki/templates/partials/theme-toggle.html +35 -0
- package/docs/themes/tanuki/templates/partials/toc-overlay.html +146 -0
- package/docs/themes/tanuki/templates/partials/version-picker.html +38 -0
- package/docs/themes/tanuki/templates/print.html +244 -0
- package/docs/themes/tanuki/templates/section.html +186 -0
- package/docs/themes/tanuki/templates/taxonomy_list.html +18 -0
- package/docs/themes/tanuki/templates/taxonomy_single.html +31 -0
- package/docs/themes/tanuki/theme.toml +58 -0
- package/examples/hello-agent.rs +55 -0
- package/package.json +36 -0
- package/proto/config.proto +60 -0
- package/proto/events.proto +0 -0
- package/proto/runtime.proto +215 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
//! Protected Runner
|
|
2
|
+
//!
|
|
3
|
+
//! Wraps Runner with input processor pipeline to validate/transform input
|
|
4
|
+
//! before execution begins.
|
|
5
|
+
//!
|
|
6
|
+
//! @see docs/TECHNICAL/17-GUARDRAILS-PROTECTION.md
|
|
7
|
+
//! @see docs/TECHNICAL/25-STREAM-PROCESSORS.md
|
|
8
|
+
|
|
9
|
+
use super::execution_runner::Runner;
|
|
10
|
+
use crate::callable::Callable;
|
|
11
|
+
use crate::graph::{CheckpointStore, CompiledGraph, NodeState};
|
|
12
|
+
use crate::kernel::ExecutionId;
|
|
13
|
+
use crate::policy::{
|
|
14
|
+
InputProcessor, InputProcessorPipeline, InputProcessorResult, PolicyAction, PolicyContext,
|
|
15
|
+
};
|
|
16
|
+
use crate::streaming::{EventEmitter, ProtectedEventEmitter};
|
|
17
|
+
use std::sync::Arc;
|
|
18
|
+
|
|
19
|
+
/// Error returned when input is blocked by processors
|
|
20
|
+
#[derive(Debug, Clone)]
|
|
21
|
+
pub struct InputBlockedError {
|
|
22
|
+
pub reason: String,
|
|
23
|
+
pub processor: String,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
impl std::fmt::Display for InputBlockedError {
|
|
27
|
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
28
|
+
write!(f, "Input blocked by {}: {}", self.processor, self.reason)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
impl std::error::Error for InputBlockedError {}
|
|
33
|
+
|
|
34
|
+
/// Protected Runner
|
|
35
|
+
///
|
|
36
|
+
/// Wraps a Runner with input processor pipeline and optional protected emitter.
|
|
37
|
+
/// Input is validated/transformed BEFORE execution begins.
|
|
38
|
+
///
|
|
39
|
+
/// ## Usage
|
|
40
|
+
///
|
|
41
|
+
/// ```ignore
|
|
42
|
+
/// use enact_core::runner::{ProtectedRunner, DefaultRunner};
|
|
43
|
+
/// use enact_core::policy::{PiiInputProcessor, PiiInputMode};
|
|
44
|
+
///
|
|
45
|
+
/// let runner = DefaultRunner::default_new();
|
|
46
|
+
/// let protected = ProtectedRunner::new(runner)
|
|
47
|
+
/// .with_input_processor(Arc::new(
|
|
48
|
+
/// PiiInputProcessor::new().with_mode(PiiInputMode::BlockDirect)
|
|
49
|
+
/// ));
|
|
50
|
+
///
|
|
51
|
+
/// // Input will be validated before callable runs
|
|
52
|
+
/// let result = protected.run_callable(&my_callable, "user input").await;
|
|
53
|
+
/// ```
|
|
54
|
+
pub struct ProtectedRunner<S: CheckpointStore> {
|
|
55
|
+
inner: Runner<S>,
|
|
56
|
+
input_pipeline: InputProcessorPipeline,
|
|
57
|
+
protected_emitter: Option<ProtectedEventEmitter>,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
impl<S: CheckpointStore> ProtectedRunner<S> {
|
|
61
|
+
/// Create a new protected runner wrapping an existing runner
|
|
62
|
+
pub fn new(runner: Runner<S>) -> Self {
|
|
63
|
+
Self {
|
|
64
|
+
inner: runner,
|
|
65
|
+
input_pipeline: InputProcessorPipeline::new(),
|
|
66
|
+
protected_emitter: None,
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/// Add an input processor to the pipeline
|
|
71
|
+
pub fn with_input_processor(mut self, processor: Arc<dyn InputProcessor>) -> Self {
|
|
72
|
+
self.input_pipeline = self.input_pipeline.add(processor);
|
|
73
|
+
self
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/// Set a protected emitter for output processing
|
|
77
|
+
pub fn with_protected_emitter(mut self, emitter: ProtectedEventEmitter) -> Self {
|
|
78
|
+
self.protected_emitter = Some(emitter);
|
|
79
|
+
self
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/// Get the execution ID
|
|
83
|
+
pub fn execution_id(&self) -> &ExecutionId {
|
|
84
|
+
self.inner.execution_id()
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/// Get the event emitter (from inner runner)
|
|
88
|
+
pub fn emitter(&self) -> &EventEmitter {
|
|
89
|
+
self.inner.emitter()
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/// Cancel the run
|
|
93
|
+
pub fn cancel(&self) {
|
|
94
|
+
self.inner.cancel();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/// Check if cancelled
|
|
98
|
+
pub fn is_cancelled(&self) -> bool {
|
|
99
|
+
self.inner.is_cancelled()
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/// Pause the run
|
|
103
|
+
pub async fn pause(&self) -> anyhow::Result<()> {
|
|
104
|
+
self.inner.pause().await
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/// Resume the run
|
|
108
|
+
pub fn resume(&self) {
|
|
109
|
+
self.inner.resume();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/// Check if paused
|
|
113
|
+
pub fn is_paused(&self) -> bool {
|
|
114
|
+
self.inner.is_paused()
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/// Create policy context for input processing
|
|
118
|
+
fn create_policy_context(&self) -> PolicyContext {
|
|
119
|
+
PolicyContext {
|
|
120
|
+
tenant_id: None, // Could be set from runner context
|
|
121
|
+
user_id: None, // Could be set from runner context
|
|
122
|
+
action: PolicyAction::StartExecution { graph_id: None },
|
|
123
|
+
metadata: std::collections::HashMap::new(),
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/// Process input through the pipeline
|
|
128
|
+
async fn process_input(&self, input: &str) -> anyhow::Result<String> {
|
|
129
|
+
if self.input_pipeline.is_empty() {
|
|
130
|
+
return Ok(input.to_string());
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
let ctx = self.create_policy_context();
|
|
134
|
+
let result = self.input_pipeline.process(input, &ctx).await?;
|
|
135
|
+
|
|
136
|
+
match result {
|
|
137
|
+
InputProcessorResult::Pass => Ok(input.to_string()),
|
|
138
|
+
InputProcessorResult::Block { reason, processor } => {
|
|
139
|
+
Err(InputBlockedError { reason, processor }.into())
|
|
140
|
+
}
|
|
141
|
+
InputProcessorResult::Modify { modified, .. } => Ok(modified),
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/// Run a callable with input validation
|
|
146
|
+
pub async fn run_callable<A: Callable>(
|
|
147
|
+
&mut self,
|
|
148
|
+
callable: &A,
|
|
149
|
+
input: &str,
|
|
150
|
+
) -> anyhow::Result<String> {
|
|
151
|
+
// Process input through pipeline
|
|
152
|
+
let processed_input = self.process_input(input).await?;
|
|
153
|
+
|
|
154
|
+
// Run with processed input
|
|
155
|
+
self.inner.run_callable(callable, &processed_input).await
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/// Run a graph with input validation
|
|
159
|
+
pub async fn run_graph(
|
|
160
|
+
&mut self,
|
|
161
|
+
graph: &CompiledGraph,
|
|
162
|
+
input: &str,
|
|
163
|
+
) -> anyhow::Result<NodeState> {
|
|
164
|
+
// Process input through pipeline
|
|
165
|
+
let processed_input = self.process_input(input).await?;
|
|
166
|
+
|
|
167
|
+
// Run with processed input
|
|
168
|
+
self.inner.run_graph(graph, &processed_input).await
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/// Protected Runner with in-memory checkpoint store (default)
|
|
173
|
+
pub type DefaultProtectedRunner = ProtectedRunner<crate::graph::InMemoryCheckpointStore>;
|
|
174
|
+
|
|
175
|
+
impl DefaultProtectedRunner {
|
|
176
|
+
/// Create a new protected runner with in-memory checkpoint store
|
|
177
|
+
pub fn default_new() -> Self {
|
|
178
|
+
Self::new(crate::runner::DefaultRunner::default_new())
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
#[cfg(test)]
|
|
183
|
+
mod tests {
|
|
184
|
+
use super::*;
|
|
185
|
+
use crate::runner::DefaultRunner;
|
|
186
|
+
use async_trait::async_trait;
|
|
187
|
+
|
|
188
|
+
struct MockCallable {
|
|
189
|
+
response: String,
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
impl MockCallable {
|
|
193
|
+
fn new(response: &str) -> Self {
|
|
194
|
+
Self {
|
|
195
|
+
response: response.to_string(),
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
#[async_trait]
|
|
201
|
+
impl Callable for MockCallable {
|
|
202
|
+
fn name(&self) -> &str {
|
|
203
|
+
"mock"
|
|
204
|
+
}
|
|
205
|
+
async fn run(&self, _input: &str) -> anyhow::Result<String> {
|
|
206
|
+
Ok(self.response.clone())
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
struct BlockingProcessor;
|
|
211
|
+
|
|
212
|
+
#[async_trait]
|
|
213
|
+
impl InputProcessor for BlockingProcessor {
|
|
214
|
+
fn name(&self) -> &str {
|
|
215
|
+
"blocker"
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async fn process(
|
|
219
|
+
&self,
|
|
220
|
+
_input: &str,
|
|
221
|
+
_ctx: &PolicyContext,
|
|
222
|
+
) -> anyhow::Result<InputProcessorResult> {
|
|
223
|
+
Ok(InputProcessorResult::Block {
|
|
224
|
+
reason: "Always blocks".to_string(),
|
|
225
|
+
processor: "blocker".to_string(),
|
|
226
|
+
})
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
#[tokio::test]
|
|
231
|
+
async fn test_protected_runner_no_processors() {
|
|
232
|
+
let runner = DefaultRunner::default_new();
|
|
233
|
+
let mut protected = ProtectedRunner::new(runner);
|
|
234
|
+
let callable = MockCallable::new("response");
|
|
235
|
+
|
|
236
|
+
let result = protected.run_callable(&callable, "input").await;
|
|
237
|
+
assert!(result.is_ok());
|
|
238
|
+
assert_eq!(result.unwrap(), "response");
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
#[tokio::test]
|
|
242
|
+
async fn test_protected_runner_blocked_input() {
|
|
243
|
+
let runner = DefaultRunner::default_new();
|
|
244
|
+
let mut protected =
|
|
245
|
+
ProtectedRunner::new(runner).with_input_processor(Arc::new(BlockingProcessor));
|
|
246
|
+
let callable = MockCallable::new("response");
|
|
247
|
+
|
|
248
|
+
let result = protected.run_callable(&callable, "input").await;
|
|
249
|
+
assert!(result.is_err());
|
|
250
|
+
assert!(result.unwrap_err().to_string().contains("blocked"));
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
#[tokio::test]
|
|
254
|
+
async fn test_protected_runner_execution_id() {
|
|
255
|
+
let runner = DefaultRunner::default_new();
|
|
256
|
+
let protected = ProtectedRunner::new(runner);
|
|
257
|
+
|
|
258
|
+
// Should have a valid execution ID
|
|
259
|
+
assert!(!protected.execution_id().as_str().is_empty());
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
#[tokio::test]
|
|
263
|
+
async fn test_protected_runner_cancel() {
|
|
264
|
+
let runner = DefaultRunner::default_new();
|
|
265
|
+
let protected = ProtectedRunner::new(runner);
|
|
266
|
+
|
|
267
|
+
assert!(!protected.is_cancelled());
|
|
268
|
+
protected.cancel();
|
|
269
|
+
assert!(protected.is_cancelled());
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
#[tokio::test]
|
|
273
|
+
async fn test_default_protected_runner() {
|
|
274
|
+
let mut protected = DefaultProtectedRunner::default_new();
|
|
275
|
+
let callable = MockCallable::new("hello");
|
|
276
|
+
|
|
277
|
+
let result = protected.run_callable(&callable, "test").await;
|
|
278
|
+
assert!(result.is_ok());
|
|
279
|
+
}
|
|
280
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
//! In-memory SignalBus implementation for local/desktop/testing
|
|
2
|
+
//!
|
|
3
|
+
//! This is the default, in-memory-only implementation suitable for:
|
|
4
|
+
//! - Local development
|
|
5
|
+
//! - Desktop applications
|
|
6
|
+
//! - Testing environments
|
|
7
|
+
//! - Single-process deployments
|
|
8
|
+
//!
|
|
9
|
+
//! ## Important
|
|
10
|
+
//!
|
|
11
|
+
//! This implementation is **not distributed** and does **not** provide
|
|
12
|
+
//! cross-process signaling. For distributed signaling, use the control plane
|
|
13
|
+
//! which implements authoritative messaging (Redis, Kafka, etc.).
|
|
14
|
+
|
|
15
|
+
use super::{SignalBus, SignalReceiver};
|
|
16
|
+
use async_trait::async_trait;
|
|
17
|
+
use std::collections::HashMap;
|
|
18
|
+
use std::sync::Arc;
|
|
19
|
+
use tokio::sync::{broadcast, RwLock};
|
|
20
|
+
|
|
21
|
+
/// In-memory signal bus using tokio broadcast channels
|
|
22
|
+
///
|
|
23
|
+
/// Suitable for desktop apps and testing (no external dependencies).
|
|
24
|
+
/// Signals are best-effort and may be lost if no receivers are subscribed.
|
|
25
|
+
pub struct InMemorySignalBus {
|
|
26
|
+
channels: Arc<RwLock<HashMap<String, broadcast::Sender<Vec<u8>>>>>,
|
|
27
|
+
capacity: usize,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
impl InMemorySignalBus {
|
|
31
|
+
/// Create a new in-memory signal bus with the specified channel capacity
|
|
32
|
+
pub fn new(capacity: usize) -> Self {
|
|
33
|
+
Self {
|
|
34
|
+
channels: Arc::new(RwLock::new(HashMap::new())),
|
|
35
|
+
capacity,
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/// Create a new in-memory signal bus with default capacity (1024)
|
|
40
|
+
pub fn default() -> Self {
|
|
41
|
+
Self::new(1024)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
#[async_trait]
|
|
46
|
+
impl SignalBus for InMemorySignalBus {
|
|
47
|
+
async fn emit(&self, channel: &str, signal: &[u8]) -> anyhow::Result<()> {
|
|
48
|
+
let channels = self.channels.read().await;
|
|
49
|
+
if let Some(sender) = channels.get(channel) {
|
|
50
|
+
// Ignore send errors (no receivers) - this is best-effort
|
|
51
|
+
let _ = sender.send(signal.to_vec());
|
|
52
|
+
}
|
|
53
|
+
Ok(())
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async fn subscribe(&self, channel: &str) -> anyhow::Result<SignalReceiver<Vec<u8>>> {
|
|
57
|
+
let mut channels = self.channels.write().await;
|
|
58
|
+
let sender = channels
|
|
59
|
+
.entry(channel.to_string())
|
|
60
|
+
.or_insert_with(|| broadcast::channel(self.capacity).0);
|
|
61
|
+
Ok(sender.subscribe())
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async fn unsubscribe(&self, _channel: &str) -> anyhow::Result<()> {
|
|
65
|
+
// Broadcast receivers auto-cleanup when dropped
|
|
66
|
+
Ok(())
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
#[cfg(test)]
|
|
71
|
+
mod tests {
|
|
72
|
+
use super::*;
|
|
73
|
+
|
|
74
|
+
#[tokio::test]
|
|
75
|
+
async fn test_inmemory_signal_bus_new() {
|
|
76
|
+
let bus = InMemorySignalBus::new(100);
|
|
77
|
+
assert_eq!(bus.capacity, 100);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
#[tokio::test]
|
|
81
|
+
async fn test_inmemory_signal_bus_default() {
|
|
82
|
+
let bus = InMemorySignalBus::default();
|
|
83
|
+
assert_eq!(bus.capacity, 1024);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
#[tokio::test]
|
|
87
|
+
async fn test_subscribe_and_receive() {
|
|
88
|
+
let bus = InMemorySignalBus::default();
|
|
89
|
+
|
|
90
|
+
// Subscribe to a channel
|
|
91
|
+
let mut rx = bus.subscribe("test-channel").await.unwrap();
|
|
92
|
+
|
|
93
|
+
// Emit a signal
|
|
94
|
+
bus.emit("test-channel", b"hello world").await.unwrap();
|
|
95
|
+
|
|
96
|
+
// Receive the signal
|
|
97
|
+
let received = rx.recv().await.unwrap();
|
|
98
|
+
assert_eq!(received, b"hello world".to_vec());
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
#[tokio::test]
|
|
102
|
+
async fn test_multiple_subscribers() {
|
|
103
|
+
let bus = InMemorySignalBus::default();
|
|
104
|
+
|
|
105
|
+
// Subscribe two receivers to the same channel
|
|
106
|
+
let mut rx1 = bus.subscribe("multi-channel").await.unwrap();
|
|
107
|
+
let mut rx2 = bus.subscribe("multi-channel").await.unwrap();
|
|
108
|
+
|
|
109
|
+
// Emit a signal
|
|
110
|
+
bus.emit("multi-channel", b"broadcast").await.unwrap();
|
|
111
|
+
|
|
112
|
+
// Both receivers should get the signal
|
|
113
|
+
let received1 = rx1.recv().await.unwrap();
|
|
114
|
+
let received2 = rx2.recv().await.unwrap();
|
|
115
|
+
|
|
116
|
+
assert_eq!(received1, b"broadcast".to_vec());
|
|
117
|
+
assert_eq!(received2, b"broadcast".to_vec());
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
#[tokio::test]
|
|
121
|
+
async fn test_emit_without_subscribers() {
|
|
122
|
+
let bus = InMemorySignalBus::default();
|
|
123
|
+
|
|
124
|
+
// Emit to a channel with no subscribers - should not fail
|
|
125
|
+
let result = bus.emit("no-subscribers", b"data").await;
|
|
126
|
+
assert!(result.is_ok());
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
#[tokio::test]
|
|
130
|
+
async fn test_emit_to_different_channels() {
|
|
131
|
+
let bus = InMemorySignalBus::default();
|
|
132
|
+
|
|
133
|
+
let mut rx1 = bus.subscribe("channel-a").await.unwrap();
|
|
134
|
+
let mut rx2 = bus.subscribe("channel-b").await.unwrap();
|
|
135
|
+
|
|
136
|
+
bus.emit("channel-a", b"msg-a").await.unwrap();
|
|
137
|
+
bus.emit("channel-b", b"msg-b").await.unwrap();
|
|
138
|
+
|
|
139
|
+
// Each receiver should only get their channel's messages
|
|
140
|
+
let received1 = rx1.recv().await.unwrap();
|
|
141
|
+
let received2 = rx2.recv().await.unwrap();
|
|
142
|
+
|
|
143
|
+
assert_eq!(received1, b"msg-a".to_vec());
|
|
144
|
+
assert_eq!(received2, b"msg-b".to_vec());
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
#[tokio::test]
|
|
148
|
+
async fn test_unsubscribe() {
|
|
149
|
+
let bus = InMemorySignalBus::default();
|
|
150
|
+
|
|
151
|
+
let _rx = bus.subscribe("unsub-channel").await.unwrap();
|
|
152
|
+
|
|
153
|
+
// Unsubscribe should succeed (no-op for broadcast)
|
|
154
|
+
let result = bus.unsubscribe("unsub-channel").await;
|
|
155
|
+
assert!(result.is_ok());
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
#[tokio::test]
|
|
159
|
+
async fn test_multiple_messages() {
|
|
160
|
+
let bus = InMemorySignalBus::default();
|
|
161
|
+
|
|
162
|
+
let mut rx = bus.subscribe("multi-msg").await.unwrap();
|
|
163
|
+
|
|
164
|
+
bus.emit("multi-msg", b"first").await.unwrap();
|
|
165
|
+
bus.emit("multi-msg", b"second").await.unwrap();
|
|
166
|
+
bus.emit("multi-msg", b"third").await.unwrap();
|
|
167
|
+
|
|
168
|
+
assert_eq!(rx.recv().await.unwrap(), b"first".to_vec());
|
|
169
|
+
assert_eq!(rx.recv().await.unwrap(), b"second".to_vec());
|
|
170
|
+
assert_eq!(rx.recv().await.unwrap(), b"third".to_vec());
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
#[tokio::test]
|
|
174
|
+
async fn test_late_subscriber_misses_messages() {
|
|
175
|
+
let bus = InMemorySignalBus::default();
|
|
176
|
+
|
|
177
|
+
// Subscribe first receiver
|
|
178
|
+
let mut rx1 = bus.subscribe("late-sub").await.unwrap();
|
|
179
|
+
|
|
180
|
+
// Emit first message
|
|
181
|
+
bus.emit("late-sub", b"early").await.unwrap();
|
|
182
|
+
|
|
183
|
+
// Subscribe second receiver (late)
|
|
184
|
+
let mut rx2 = bus.subscribe("late-sub").await.unwrap();
|
|
185
|
+
|
|
186
|
+
// Emit second message
|
|
187
|
+
bus.emit("late-sub", b"late").await.unwrap();
|
|
188
|
+
|
|
189
|
+
// First receiver gets both
|
|
190
|
+
assert_eq!(rx1.recv().await.unwrap(), b"early".to_vec());
|
|
191
|
+
assert_eq!(rx1.recv().await.unwrap(), b"late".to_vec());
|
|
192
|
+
|
|
193
|
+
// Second receiver only gets the late message
|
|
194
|
+
assert_eq!(rx2.recv().await.unwrap(), b"late".to_vec());
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
#[tokio::test]
|
|
198
|
+
async fn test_concurrent_emit() {
|
|
199
|
+
let bus = Arc::new(InMemorySignalBus::default());
|
|
200
|
+
|
|
201
|
+
let mut rx = bus.subscribe("concurrent").await.unwrap();
|
|
202
|
+
|
|
203
|
+
let bus1 = bus.clone();
|
|
204
|
+
let bus2 = bus.clone();
|
|
205
|
+
|
|
206
|
+
// Spawn two tasks emitting concurrently
|
|
207
|
+
let h1 = tokio::spawn(async move {
|
|
208
|
+
for i in 0..5 {
|
|
209
|
+
bus1.emit("concurrent", format!("msg-a-{}", i).as_bytes()).await.unwrap();
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
let h2 = tokio::spawn(async move {
|
|
214
|
+
for i in 0..5 {
|
|
215
|
+
bus2.emit("concurrent", format!("msg-b-{}", i).as_bytes()).await.unwrap();
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
h1.await.unwrap();
|
|
220
|
+
h2.await.unwrap();
|
|
221
|
+
|
|
222
|
+
// Collect all received messages
|
|
223
|
+
let mut received = Vec::new();
|
|
224
|
+
while let Ok(msg) = rx.try_recv() {
|
|
225
|
+
received.push(msg);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Should have received 10 messages total
|
|
229
|
+
assert_eq!(received.len(), 10);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
//! Signal module - Optional, best-effort signaling for hints and notifications
|
|
2
|
+
//!
|
|
3
|
+
//! ## Important: Non-Authoritative
|
|
4
|
+
//!
|
|
5
|
+
//! This module provides **optional, best-effort signaling** for hints and
|
|
6
|
+
//! notifications. Signals are **not authoritative** and the kernel must work
|
|
7
|
+
//! correctly even if all signaling infrastructure is disabled.
|
|
8
|
+
//!
|
|
9
|
+
//! ## Three Kinds of "Messages"
|
|
10
|
+
//!
|
|
11
|
+
//! There are three distinct kinds of communication in the system:
|
|
12
|
+
//!
|
|
13
|
+
//! | Kind | Example | Where it belongs |
|
|
14
|
+
//! | -------------------------- | ------------------------------- | ----------------------- |
|
|
15
|
+
//! | **Execution events** | step started, decision made | **Kernel only** (`kernel/event.rs`) |
|
|
16
|
+
//! | **Control signals** | cancel execution, scale workers | **Control plane** (cloud) |
|
|
17
|
+
//! | **Infrastructure signals** | notify UI, wake worker | **This module** (optional) |
|
|
18
|
+
//!
|
|
19
|
+
//! ## Signal Semantics
|
|
20
|
+
//!
|
|
21
|
+
//! Signals are:
|
|
22
|
+
//! - ✅ Non-authoritative (may be ignored)
|
|
23
|
+
//! - ✅ Best-effort (delivery not guaranteed)
|
|
24
|
+
//! - ✅ Optional (kernel works perfectly without them)
|
|
25
|
+
//! - ✅ For hints only (wake-up, notify, nudge)
|
|
26
|
+
//!
|
|
27
|
+
//! Signals are NOT:
|
|
28
|
+
//! - ❌ Execution events (use `kernel/event.rs`)
|
|
29
|
+
//! - ❌ State changes (use `kernel/reducer.rs`)
|
|
30
|
+
//! - ❌ Required for correctness
|
|
31
|
+
//! - ❌ Durable queues (use control plane)
|
|
32
|
+
//!
|
|
33
|
+
//! ## Invariant
|
|
34
|
+
//!
|
|
35
|
+
//! **The kernel must remain correct, deterministic, and replayable
|
|
36
|
+
//! even if all signaling infrastructure is disabled.**
|
|
37
|
+
//!
|
|
38
|
+
//! ## CRITICAL INVARIANT: Signals Never Drive Execution State
|
|
39
|
+
//!
|
|
40
|
+
//! SignalBus implementations MUST NOT have access to:
|
|
41
|
+
//! - ExecutionKernel
|
|
42
|
+
//! - Reducer
|
|
43
|
+
//! - ExecutionState
|
|
44
|
+
//!
|
|
45
|
+
//! Signals are hints only. If someone can "resume execution" via signal,
|
|
46
|
+
//! that is an architectural leak.
|
|
47
|
+
//!
|
|
48
|
+
//! **Enforcement**: SignalBus trait and implementations should not import
|
|
49
|
+
//! any kernel modules. If you find yourself needing kernel types in signal
|
|
50
|
+
//! code, you are violating this invariant.
|
|
51
|
+
//!
|
|
52
|
+
//! ## Use Cases
|
|
53
|
+
//!
|
|
54
|
+
//! ### Allowed Signal Uses
|
|
55
|
+
//! - ✅ Wake a paused execution
|
|
56
|
+
//! - ✅ Notify local UI
|
|
57
|
+
//! - ✅ Tell a worker "check for work"
|
|
58
|
+
//! - ✅ Hint that something changed
|
|
59
|
+
//!
|
|
60
|
+
//! ### Forbidden Signal Uses
|
|
61
|
+
//! - ❌ Carry execution events (use `kernel/event.rs`)
|
|
62
|
+
//! - ❌ Control execution state (use `kernel/reducer.rs`)
|
|
63
|
+
//! - ❌ Replace durable queues (use control plane)
|
|
64
|
+
//! - ❌ Drive orchestration logic (use control plane)
|
|
65
|
+
//!
|
|
66
|
+
//! ## Control Plane Messaging
|
|
67
|
+
//!
|
|
68
|
+
//! Authoritative messaging (Redis, Kafka, durable queues) lives in the
|
|
69
|
+
//! control plane (`apps/api/` or future `enact-control-plane` crate), not here.
|
|
70
|
+
//! The control plane:
|
|
71
|
+
//! - Implements durable queues
|
|
72
|
+
//! - Translates infra messages → kernel invocations
|
|
73
|
+
//! - Fans out kernel events to external systems
|
|
74
|
+
//!
|
|
75
|
+
//! The kernel does not know *how* messages travel. It only knows:
|
|
76
|
+
//! > "I was invoked."
|
|
77
|
+
|
|
78
|
+
use async_trait::async_trait;
|
|
79
|
+
use std::sync::Arc;
|
|
80
|
+
use tokio::sync::broadcast;
|
|
81
|
+
|
|
82
|
+
mod inmemory;
|
|
83
|
+
|
|
84
|
+
pub use inmemory::InMemorySignalBus;
|
|
85
|
+
|
|
86
|
+
/// Receiver for subscribed signals
|
|
87
|
+
pub type SignalReceiver<T> = broadcast::Receiver<T>;
|
|
88
|
+
|
|
89
|
+
/// SignalBus trait - Optional, best-effort signaling abstraction
|
|
90
|
+
///
|
|
91
|
+
/// This trait provides a non-authoritative signaling mechanism for hints
|
|
92
|
+
/// and notifications. Implementations should be lightweight and optional.
|
|
93
|
+
///
|
|
94
|
+
/// The kernel must work correctly even if SignalBus is disabled or fails.
|
|
95
|
+
#[async_trait]
|
|
96
|
+
pub trait SignalBus: Send + Sync {
|
|
97
|
+
/// Emit a signal to a channel (best-effort, may be ignored)
|
|
98
|
+
async fn emit(&self, channel: &str, signal: &[u8]) -> anyhow::Result<()>;
|
|
99
|
+
|
|
100
|
+
/// Subscribe to a channel, returns a receiver for signals
|
|
101
|
+
async fn subscribe(&self, channel: &str) -> anyhow::Result<SignalReceiver<Vec<u8>>>;
|
|
102
|
+
|
|
103
|
+
/// Unsubscribe from a channel
|
|
104
|
+
async fn unsubscribe(&self, channel: &str) -> anyhow::Result<()>;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/// Boxed SignalBus for dynamic dispatch
|
|
108
|
+
pub type DynSignalBus = Arc<dyn SignalBus>;
|