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,108 @@
|
|
|
1
|
+
//! Streaming - Event delivery and persistence
|
|
2
|
+
//!
|
|
3
|
+
//! This module handles **delivery** of events to consumers. It subscribes to
|
|
4
|
+
//! kernel events and delivers them via SSE, persistence, etc.
|
|
5
|
+
//!
|
|
6
|
+
//! ## Important: Source of Truth
|
|
7
|
+
//!
|
|
8
|
+
//! IDs and Events are DEFINED in `kernel/` (source of truth).
|
|
9
|
+
//! This module RE-EXPORTS them for convenience and provides:
|
|
10
|
+
//! - **event_logger.rs**: Append-only event log (EventStore, EventLog)
|
|
11
|
+
//! - **event_stream.rs**: Wire format for SSE (StreamEvent, data-* protocol)
|
|
12
|
+
//!
|
|
13
|
+
//! ## Architecture
|
|
14
|
+
//!
|
|
15
|
+
//! ```text
|
|
16
|
+
//! ExecutionKernel (owns IDs and Events)
|
|
17
|
+
//! │
|
|
18
|
+
//! │ emit events
|
|
19
|
+
//! ▼
|
|
20
|
+
//! ┌─────────────────────────────────────────────────────────┐
|
|
21
|
+
//! │ streaming/ │
|
|
22
|
+
//! │ ┌────────────┐ ┌────────────────────┐ │
|
|
23
|
+
//! │ │event_logger│ │ event_stream.rs │ │
|
|
24
|
+
//! │ │ (persist) │ │ (wire format) │ │
|
|
25
|
+
//! │ └────────────┘ └─────────┬──────────┘ │
|
|
26
|
+
//! └────────────────────────────┼────────────────────────────┘
|
|
27
|
+
//! │
|
|
28
|
+
//! ┌─────────────┼─────────────────┐
|
|
29
|
+
//! ▼ ▼ ▼
|
|
30
|
+
//! ┌──────────┐ ┌───────────┐ ┌───────────┐
|
|
31
|
+
//! │ TUI │ │ GUI (SSE) │ │ telemetry │
|
|
32
|
+
//! │(ratatui) │ │ (web) │ │ (OTel) │
|
|
33
|
+
//! └──────────┘ └───────────┘ └───────────┘
|
|
34
|
+
//! ```
|
|
35
|
+
//!
|
|
36
|
+
//! ## Event Protocol
|
|
37
|
+
//! All events use `data-*` prefix for SSE compatibility:
|
|
38
|
+
//! - Standard: `data-text-start`, `data-text-delta`, `data-finish`, etc.
|
|
39
|
+
//! - Custom: `data-execution-start`, `data-step-start`, etc.
|
|
40
|
+
//!
|
|
41
|
+
//! @see docs/TECHNICAL/01-EXECUTION-TELEMETRY.md
|
|
42
|
+
//! @see https://ai-sdk.dev/docs/ai-sdk-ui/stream-protocol
|
|
43
|
+
|
|
44
|
+
mod event_logger;
|
|
45
|
+
mod event_stream;
|
|
46
|
+
mod pause_cancel;
|
|
47
|
+
mod sse;
|
|
48
|
+
|
|
49
|
+
// =============================================================================
|
|
50
|
+
// Stream Events (wire format for SSE)
|
|
51
|
+
// =============================================================================
|
|
52
|
+
pub use event_stream::{EventEmitter, EventStream, StreamEvent, StreamMode};
|
|
53
|
+
|
|
54
|
+
// =============================================================================
|
|
55
|
+
// Event Log (persistence)
|
|
56
|
+
// =============================================================================
|
|
57
|
+
pub use event_logger::{EventLog, EventLogEntry, EventStore, InMemoryEventStore};
|
|
58
|
+
|
|
59
|
+
// =============================================================================
|
|
60
|
+
// Re-exports from kernel (source of truth)
|
|
61
|
+
// =============================================================================
|
|
62
|
+
// IDs and Events are DEFINED in kernel/. We re-export for convenience.
|
|
63
|
+
pub use crate::kernel::{
|
|
64
|
+
prefixes,
|
|
65
|
+
// IDs
|
|
66
|
+
ArtifactId,
|
|
67
|
+
ExecutionId,
|
|
68
|
+
GraphId,
|
|
69
|
+
MessageId,
|
|
70
|
+
ThreadId,
|
|
71
|
+
// Events
|
|
72
|
+
ControlAction,
|
|
73
|
+
ControlActor,
|
|
74
|
+
ControlEvent,
|
|
75
|
+
ControlOutcome,
|
|
76
|
+
DecisionAlternative,
|
|
77
|
+
DecisionInput,
|
|
78
|
+
DecisionRecord,
|
|
79
|
+
DecisionType,
|
|
80
|
+
Event,
|
|
81
|
+
ExecutionContext,
|
|
82
|
+
ExecutionEvent,
|
|
83
|
+
ExecutionEventType,
|
|
84
|
+
ModelContext,
|
|
85
|
+
NodeId,
|
|
86
|
+
ParentLink,
|
|
87
|
+
ParentType,
|
|
88
|
+
RunId,
|
|
89
|
+
StepId,
|
|
90
|
+
StepType,
|
|
91
|
+
TenantId,
|
|
92
|
+
UserId,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// =============================================================================
|
|
96
|
+
// Protection Layer (feat-09: Guardrails)
|
|
97
|
+
// =============================================================================
|
|
98
|
+
// Output processors that run BEFORE storage/streaming.
|
|
99
|
+
// @see docs/TECHNICAL/17-GUARDRAILS-PROTECTION.md
|
|
100
|
+
// @see docs/TECHNICAL/25-STREAM-PROCESSORS.md
|
|
101
|
+
mod protected_emitter;
|
|
102
|
+
pub mod protection;
|
|
103
|
+
|
|
104
|
+
pub use protected_emitter::ProtectedEventEmitter;
|
|
105
|
+
pub use protection::{
|
|
106
|
+
DataDestination, EncryptionProcessor, OutputProcessor, PiiProtectionProcessor, ProcessedEvent,
|
|
107
|
+
ProcessorPipeline, ProtectionContext,
|
|
108
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
//! Protected Event Emitter
|
|
2
|
+
//!
|
|
3
|
+
//! Wraps EventEmitter with output processor pipeline to ensure all events
|
|
4
|
+
//! pass through protection before storage/streaming.
|
|
5
|
+
//!
|
|
6
|
+
//! @see docs/TECHNICAL/17-GUARDRAILS-PROTECTION.md
|
|
7
|
+
|
|
8
|
+
use super::protection::{OutputProcessor, ProcessorPipeline, ProtectionContext};
|
|
9
|
+
use super::{EventEmitter, StreamEvent, StreamMode};
|
|
10
|
+
use std::sync::Arc;
|
|
11
|
+
|
|
12
|
+
/// Protected Event Emitter
|
|
13
|
+
///
|
|
14
|
+
/// Wraps EventEmitter with output processor pipeline.
|
|
15
|
+
/// All events pass through protection before being added to the stream.
|
|
16
|
+
///
|
|
17
|
+
/// ## Usage
|
|
18
|
+
///
|
|
19
|
+
/// ```ignore
|
|
20
|
+
/// use enact_core::streaming::{ProtectedEventEmitter, PiiProtectionProcessor};
|
|
21
|
+
///
|
|
22
|
+
/// let emitter = ProtectedEventEmitter::new()
|
|
23
|
+
/// .with_processor(Arc::new(PiiProtectionProcessor::new()))
|
|
24
|
+
/// .with_context(ProtectionContext::for_stream());
|
|
25
|
+
/// ```
|
|
26
|
+
pub struct ProtectedEventEmitter {
|
|
27
|
+
inner: EventEmitter,
|
|
28
|
+
pipeline: ProcessorPipeline,
|
|
29
|
+
context: ProtectionContext,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
impl ProtectedEventEmitter {
|
|
33
|
+
/// Create a new protected event emitter
|
|
34
|
+
pub fn new() -> Self {
|
|
35
|
+
Self {
|
|
36
|
+
inner: EventEmitter::new(),
|
|
37
|
+
pipeline: ProcessorPipeline::new(),
|
|
38
|
+
context: ProtectionContext::for_stream(),
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/// Create with a specific stream mode
|
|
43
|
+
pub fn with_mode(mode: StreamMode) -> Self {
|
|
44
|
+
Self {
|
|
45
|
+
inner: EventEmitter::with_mode(mode),
|
|
46
|
+
pipeline: ProcessorPipeline::new(),
|
|
47
|
+
context: ProtectionContext::for_stream(),
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/// Add a processor to the pipeline
|
|
52
|
+
pub fn with_processor(mut self, processor: Arc<dyn OutputProcessor>) -> Self {
|
|
53
|
+
self.pipeline = self.pipeline.add(processor);
|
|
54
|
+
self
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/// Set the protection context
|
|
58
|
+
pub fn with_context(mut self, context: ProtectionContext) -> Self {
|
|
59
|
+
self.context = context;
|
|
60
|
+
self
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/// Set the protection context (mutable)
|
|
64
|
+
pub fn set_context(&mut self, context: ProtectionContext) {
|
|
65
|
+
self.context = context;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/// Get the protection context
|
|
69
|
+
pub fn context(&self) -> &ProtectionContext {
|
|
70
|
+
&self.context
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/// Emit an event (runs through protection pipeline)
|
|
74
|
+
///
|
|
75
|
+
/// This is async because processors may need async operations
|
|
76
|
+
pub async fn emit(&self, event: StreamEvent) -> anyhow::Result<()> {
|
|
77
|
+
if self.pipeline.is_empty() {
|
|
78
|
+
// No processors, emit directly
|
|
79
|
+
self.inner.emit(event);
|
|
80
|
+
return Ok(());
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Run through protection pipeline
|
|
84
|
+
let processed = self.pipeline.process(event, &self.context).await?;
|
|
85
|
+
self.inner.emit(processed.event);
|
|
86
|
+
Ok(())
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/// Emit an event synchronously (bypasses protection pipeline)
|
|
90
|
+
///
|
|
91
|
+
/// Use only for events that are guaranteed safe (control events, etc.)
|
|
92
|
+
pub fn emit_unprotected(&self, event: StreamEvent) {
|
|
93
|
+
self.inner.emit(event);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/// Emit an event unconditionally (ignores mode, runs through protection)
|
|
97
|
+
pub async fn emit_force(&self, event: StreamEvent) -> anyhow::Result<()> {
|
|
98
|
+
if self.pipeline.is_empty() {
|
|
99
|
+
self.inner.emit_force(event);
|
|
100
|
+
return Ok(());
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
let processed = self.pipeline.process(event, &self.context).await?;
|
|
104
|
+
self.inner.emit_force(processed.event);
|
|
105
|
+
Ok(())
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/// Get all collected events
|
|
109
|
+
pub fn drain(&self) -> Vec<StreamEvent> {
|
|
110
|
+
self.inner.drain()
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/// Get the current stream mode
|
|
114
|
+
pub fn mode(&self) -> StreamMode {
|
|
115
|
+
self.inner.mode()
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/// Get reference to inner emitter (for compatibility)
|
|
119
|
+
pub fn inner(&self) -> &EventEmitter {
|
|
120
|
+
&self.inner
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
impl Default for ProtectedEventEmitter {
|
|
125
|
+
fn default() -> Self {
|
|
126
|
+
Self::new()
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
#[cfg(test)]
|
|
131
|
+
mod tests {
|
|
132
|
+
use super::*;
|
|
133
|
+
use crate::kernel::ExecutionId;
|
|
134
|
+
|
|
135
|
+
#[tokio::test]
|
|
136
|
+
async fn test_protected_emitter_no_processors() {
|
|
137
|
+
let emitter = ProtectedEventEmitter::new();
|
|
138
|
+
let exec_id = ExecutionId::new();
|
|
139
|
+
|
|
140
|
+
emitter
|
|
141
|
+
.emit(StreamEvent::execution_start(&exec_id))
|
|
142
|
+
.await
|
|
143
|
+
.unwrap();
|
|
144
|
+
|
|
145
|
+
let events = emitter.drain();
|
|
146
|
+
assert_eq!(events.len(), 1);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
#[tokio::test]
|
|
150
|
+
async fn test_protected_emitter_emit_unprotected() {
|
|
151
|
+
let emitter = ProtectedEventEmitter::new();
|
|
152
|
+
let exec_id = ExecutionId::new();
|
|
153
|
+
|
|
154
|
+
// Emit synchronously
|
|
155
|
+
emitter.emit_unprotected(StreamEvent::execution_start(&exec_id));
|
|
156
|
+
|
|
157
|
+
let events = emitter.drain();
|
|
158
|
+
assert_eq!(events.len(), 1);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
#[tokio::test]
|
|
162
|
+
async fn test_protected_emitter_context() {
|
|
163
|
+
let emitter = ProtectedEventEmitter::new().with_context(ProtectionContext::for_storage());
|
|
164
|
+
|
|
165
|
+
assert!(emitter.context().destination.requires_encryption());
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
#[tokio::test]
|
|
169
|
+
async fn test_protected_emitter_mode() {
|
|
170
|
+
let emitter = ProtectedEventEmitter::with_mode(StreamMode::Summary);
|
|
171
|
+
assert_eq!(emitter.mode(), StreamMode::Summary);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
//! Protection Context
|
|
2
|
+
//!
|
|
3
|
+
//! Context types that determine how data is protected based on its destination.
|
|
4
|
+
|
|
5
|
+
use crate::kernel::TenantId;
|
|
6
|
+
|
|
7
|
+
/// Data destination - where the data is going
|
|
8
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
9
|
+
pub enum DataDestination {
|
|
10
|
+
/// Storage (PostgreSQL, Blob Storage, EventStore)
|
|
11
|
+
/// Treatment: Encrypt sensitive fields
|
|
12
|
+
Storage,
|
|
13
|
+
|
|
14
|
+
/// Streaming to frontend (SSE, gRPC)
|
|
15
|
+
/// Treatment: Mask PII, never send encrypted or raw
|
|
16
|
+
Stream,
|
|
17
|
+
|
|
18
|
+
/// Structured logs (telemetry, observability)
|
|
19
|
+
/// Treatment: Hash or mask, never raw
|
|
20
|
+
Log,
|
|
21
|
+
|
|
22
|
+
/// Audit export (JSON, PDF)
|
|
23
|
+
/// Treatment: Decrypt for internal, mask for external
|
|
24
|
+
AuditExport,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
impl DataDestination {
|
|
28
|
+
/// Returns true if this destination is frontend-visible
|
|
29
|
+
pub fn is_frontend_visible(&self) -> bool {
|
|
30
|
+
matches!(self, DataDestination::Stream)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/// Returns true if this destination requires encryption
|
|
34
|
+
pub fn requires_encryption(&self) -> bool {
|
|
35
|
+
matches!(self, DataDestination::Storage)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/// Protection context - information needed to make protection decisions
|
|
40
|
+
#[derive(Debug, Clone)]
|
|
41
|
+
pub struct ProtectionContext {
|
|
42
|
+
/// Where the data is going
|
|
43
|
+
pub destination: DataDestination,
|
|
44
|
+
|
|
45
|
+
/// Tenant context (for tenant-specific policies)
|
|
46
|
+
pub tenant_id: Option<TenantId>,
|
|
47
|
+
|
|
48
|
+
/// Whether this is an internal audit export (can decrypt)
|
|
49
|
+
pub is_internal_audit: bool,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
impl ProtectionContext {
|
|
53
|
+
/// Create a new protection context
|
|
54
|
+
pub fn new(destination: DataDestination) -> Self {
|
|
55
|
+
Self {
|
|
56
|
+
destination,
|
|
57
|
+
tenant_id: None,
|
|
58
|
+
is_internal_audit: false,
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/// Create context for streaming to frontend
|
|
63
|
+
pub fn for_stream() -> Self {
|
|
64
|
+
Self::new(DataDestination::Stream)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/// Create context for storage
|
|
68
|
+
pub fn for_storage() -> Self {
|
|
69
|
+
Self::new(DataDestination::Storage)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/// Create context for logging
|
|
73
|
+
pub fn for_log() -> Self {
|
|
74
|
+
Self::new(DataDestination::Log)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/// Create context for audit export
|
|
78
|
+
pub fn for_audit(is_internal: bool) -> Self {
|
|
79
|
+
Self {
|
|
80
|
+
destination: DataDestination::AuditExport,
|
|
81
|
+
tenant_id: None,
|
|
82
|
+
is_internal_audit: is_internal,
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/// Add tenant context
|
|
87
|
+
pub fn with_tenant(mut self, tenant_id: TenantId) -> Self {
|
|
88
|
+
self.tenant_id = Some(tenant_id);
|
|
89
|
+
self
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
#[cfg(test)]
|
|
94
|
+
mod tests {
|
|
95
|
+
use super::*;
|
|
96
|
+
|
|
97
|
+
#[test]
|
|
98
|
+
fn test_data_destination_frontend_visible() {
|
|
99
|
+
assert!(DataDestination::Stream.is_frontend_visible());
|
|
100
|
+
assert!(!DataDestination::Storage.is_frontend_visible());
|
|
101
|
+
assert!(!DataDestination::Log.is_frontend_visible());
|
|
102
|
+
assert!(!DataDestination::AuditExport.is_frontend_visible());
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
#[test]
|
|
106
|
+
fn test_data_destination_requires_encryption() {
|
|
107
|
+
assert!(DataDestination::Storage.requires_encryption());
|
|
108
|
+
assert!(!DataDestination::Stream.requires_encryption());
|
|
109
|
+
assert!(!DataDestination::Log.requires_encryption());
|
|
110
|
+
assert!(!DataDestination::AuditExport.requires_encryption());
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
#[test]
|
|
114
|
+
fn test_protection_context_factories() {
|
|
115
|
+
let stream_ctx = ProtectionContext::for_stream();
|
|
116
|
+
assert_eq!(stream_ctx.destination, DataDestination::Stream);
|
|
117
|
+
|
|
118
|
+
let storage_ctx = ProtectionContext::for_storage();
|
|
119
|
+
assert_eq!(storage_ctx.destination, DataDestination::Storage);
|
|
120
|
+
|
|
121
|
+
let log_ctx = ProtectionContext::for_log();
|
|
122
|
+
assert_eq!(log_ctx.destination, DataDestination::Log);
|
|
123
|
+
|
|
124
|
+
let audit_ctx = ProtectionContext::for_audit(true);
|
|
125
|
+
assert_eq!(audit_ctx.destination, DataDestination::AuditExport);
|
|
126
|
+
assert!(audit_ctx.is_internal_audit);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
#[test]
|
|
130
|
+
fn test_protection_context_with_tenant() {
|
|
131
|
+
let tenant_id = TenantId::from_string("tenant_123");
|
|
132
|
+
let ctx = ProtectionContext::for_stream().with_tenant(tenant_id);
|
|
133
|
+
assert!(ctx.tenant_id.is_some());
|
|
134
|
+
assert_eq!(ctx.tenant_id.unwrap().as_str(), "tenant_123");
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
//! Encryption Processor
|
|
2
|
+
//!
|
|
3
|
+
//! Output processor that encrypts sensitive data for storage destinations.
|
|
4
|
+
//! Uses AES-256-GCM for symmetric encryption.
|
|
5
|
+
//!
|
|
6
|
+
//! @see docs/TECHNICAL/17-GUARDRAILS-PROTECTION.md
|
|
7
|
+
|
|
8
|
+
use super::context::ProtectionContext;
|
|
9
|
+
use super::processor::{OutputProcessor, ProcessedEvent};
|
|
10
|
+
use crate::streaming::StreamEvent;
|
|
11
|
+
use aes_gcm::{
|
|
12
|
+
aead::{Aead, KeyInit},
|
|
13
|
+
Aes256Gcm, Nonce,
|
|
14
|
+
};
|
|
15
|
+
use async_trait::async_trait;
|
|
16
|
+
use rand::RngCore;
|
|
17
|
+
|
|
18
|
+
/// 256-bit encryption key
|
|
19
|
+
pub type EncryptionKey = [u8; 32];
|
|
20
|
+
|
|
21
|
+
/// Encryption processor for storage destinations
|
|
22
|
+
///
|
|
23
|
+
/// Encrypts text content when the destination requires encryption (Storage).
|
|
24
|
+
/// Streaming destinations receive the original (unencrypted) content.
|
|
25
|
+
///
|
|
26
|
+
/// ## Design Notes
|
|
27
|
+
///
|
|
28
|
+
/// This is a placeholder implementation. In production:
|
|
29
|
+
/// - Use proper key derivation (HKDF)
|
|
30
|
+
/// - Integrate with KMS (AWS KMS, GCP KMS, etc.)
|
|
31
|
+
/// - Use envelope encryption for large payloads
|
|
32
|
+
/// - Store nonces with ciphertext
|
|
33
|
+
pub struct EncryptionProcessor {
|
|
34
|
+
/// Whether encryption is enabled
|
|
35
|
+
enabled: bool,
|
|
36
|
+
/// Symmetric key material
|
|
37
|
+
key: EncryptionKey,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
impl EncryptionProcessor {
|
|
41
|
+
/// Create a new encryption processor (disabled by default)
|
|
42
|
+
pub fn new() -> Self {
|
|
43
|
+
Self {
|
|
44
|
+
enabled: false,
|
|
45
|
+
key: [0u8; 32],
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/// Provide a specific encryption key (32 bytes)
|
|
50
|
+
pub fn with_key(mut self, key: EncryptionKey) -> Self {
|
|
51
|
+
self.key = key;
|
|
52
|
+
self
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/// Enable encryption
|
|
56
|
+
pub fn enabled(mut self) -> Self {
|
|
57
|
+
self.enabled = true;
|
|
58
|
+
self
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/// Check if encryption is enabled
|
|
62
|
+
pub fn is_enabled(&self) -> bool {
|
|
63
|
+
self.enabled
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/// Encrypt a string using AES-256-GCM
|
|
67
|
+
fn encrypt_text(&self, text: &str) -> anyhow::Result<String> {
|
|
68
|
+
let key = aes_gcm::Key::<Aes256Gcm>::from_slice(&self.key);
|
|
69
|
+
let cipher = Aes256Gcm::new(key);
|
|
70
|
+
|
|
71
|
+
let mut nonce_bytes = [0u8; 12];
|
|
72
|
+
rand::thread_rng().fill_bytes(&mut nonce_bytes);
|
|
73
|
+
let nonce = Nonce::from_slice(&nonce_bytes);
|
|
74
|
+
|
|
75
|
+
let ciphertext = cipher
|
|
76
|
+
.encrypt(nonce, text.as_bytes())
|
|
77
|
+
.map_err(|e| anyhow::anyhow!("encryption failed: {:?}", e))?;
|
|
78
|
+
|
|
79
|
+
// Store nonce + ciphertext as hex for transport
|
|
80
|
+
let mut payload = Vec::with_capacity(nonce_bytes.len() + ciphertext.len());
|
|
81
|
+
payload.extend_from_slice(&nonce_bytes);
|
|
82
|
+
payload.extend_from_slice(&ciphertext);
|
|
83
|
+
|
|
84
|
+
Ok(format!("ENC:{}", hex::encode(payload)))
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/// Check if event has text content that should be encrypted
|
|
88
|
+
fn should_encrypt_event(&self, event: &StreamEvent, ctx: &ProtectionContext) -> bool {
|
|
89
|
+
// Only encrypt for storage destination
|
|
90
|
+
if !ctx.destination.requires_encryption() {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Check if event has sensitive content
|
|
95
|
+
matches!(
|
|
96
|
+
event,
|
|
97
|
+
StreamEvent::TextDelta { .. }
|
|
98
|
+
| StreamEvent::StepEnd {
|
|
99
|
+
output: Some(_),
|
|
100
|
+
..
|
|
101
|
+
}
|
|
102
|
+
| StreamEvent::ExecutionEnd {
|
|
103
|
+
final_output: Some(_),
|
|
104
|
+
..
|
|
105
|
+
}
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// Encrypt event content
|
|
110
|
+
fn encrypt_event(&self, event: StreamEvent) -> anyhow::Result<(StreamEvent, Option<String>)> {
|
|
111
|
+
match event {
|
|
112
|
+
StreamEvent::TextDelta { id, delta } => {
|
|
113
|
+
let encrypted = self.encrypt_text(&delta)?;
|
|
114
|
+
Ok((
|
|
115
|
+
StreamEvent::TextDelta {
|
|
116
|
+
id,
|
|
117
|
+
delta: "[ENCRYPTED]".to_string(),
|
|
118
|
+
},
|
|
119
|
+
Some(encrypted),
|
|
120
|
+
))
|
|
121
|
+
}
|
|
122
|
+
StreamEvent::StepEnd {
|
|
123
|
+
execution_id,
|
|
124
|
+
step_id,
|
|
125
|
+
output: Some(text),
|
|
126
|
+
duration_ms,
|
|
127
|
+
timestamp,
|
|
128
|
+
} => {
|
|
129
|
+
let encrypted = self.encrypt_text(&text)?;
|
|
130
|
+
Ok((
|
|
131
|
+
StreamEvent::StepEnd {
|
|
132
|
+
execution_id,
|
|
133
|
+
step_id,
|
|
134
|
+
output: Some("[ENCRYPTED]".to_string()),
|
|
135
|
+
duration_ms,
|
|
136
|
+
timestamp,
|
|
137
|
+
},
|
|
138
|
+
Some(encrypted),
|
|
139
|
+
))
|
|
140
|
+
}
|
|
141
|
+
StreamEvent::ExecutionEnd {
|
|
142
|
+
execution_id,
|
|
143
|
+
final_output: Some(text),
|
|
144
|
+
duration_ms,
|
|
145
|
+
timestamp,
|
|
146
|
+
} => {
|
|
147
|
+
let encrypted = self.encrypt_text(&text)?;
|
|
148
|
+
Ok((
|
|
149
|
+
StreamEvent::ExecutionEnd {
|
|
150
|
+
execution_id,
|
|
151
|
+
final_output: Some("[ENCRYPTED]".to_string()),
|
|
152
|
+
duration_ms,
|
|
153
|
+
timestamp,
|
|
154
|
+
},
|
|
155
|
+
Some(encrypted),
|
|
156
|
+
))
|
|
157
|
+
}
|
|
158
|
+
_ => Ok((event, None)),
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
impl Default for EncryptionProcessor {
|
|
164
|
+
fn default() -> Self {
|
|
165
|
+
Self::new()
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
#[async_trait]
|
|
170
|
+
impl OutputProcessor for EncryptionProcessor {
|
|
171
|
+
fn name(&self) -> &str {
|
|
172
|
+
"encryption"
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async fn process(
|
|
176
|
+
&self,
|
|
177
|
+
event: StreamEvent,
|
|
178
|
+
ctx: &ProtectionContext,
|
|
179
|
+
) -> anyhow::Result<ProcessedEvent> {
|
|
180
|
+
// Skip if not enabled
|
|
181
|
+
if !self.enabled {
|
|
182
|
+
return Ok(ProcessedEvent::unchanged(event));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Skip if not storage destination
|
|
186
|
+
if !self.should_encrypt_event(&event, ctx) {
|
|
187
|
+
return Ok(ProcessedEvent::unchanged(event));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Encrypt the event
|
|
191
|
+
let (encrypted_event, encrypted_payload) = self.encrypt_event(event)?;
|
|
192
|
+
|
|
193
|
+
Ok(ProcessedEvent {
|
|
194
|
+
event: encrypted_event,
|
|
195
|
+
was_modified: true,
|
|
196
|
+
encrypted_payload,
|
|
197
|
+
})
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
#[cfg(test)]
|
|
202
|
+
mod tests {
|
|
203
|
+
use super::*;
|
|
204
|
+
use crate::kernel::ExecutionId;
|
|
205
|
+
|
|
206
|
+
#[tokio::test]
|
|
207
|
+
async fn test_encryption_processor_name() {
|
|
208
|
+
let processor = EncryptionProcessor::new();
|
|
209
|
+
assert_eq!(processor.name(), "encryption");
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
#[tokio::test]
|
|
213
|
+
async fn test_encryption_processor_disabled_by_default() {
|
|
214
|
+
let processor = EncryptionProcessor::new();
|
|
215
|
+
assert!(!processor.is_enabled());
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
#[tokio::test]
|
|
219
|
+
async fn test_encryption_processor_can_enable() {
|
|
220
|
+
let processor = EncryptionProcessor::new().enabled();
|
|
221
|
+
assert!(processor.is_enabled());
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
#[tokio::test]
|
|
225
|
+
async fn test_encryption_skips_when_disabled() {
|
|
226
|
+
let processor = EncryptionProcessor::new();
|
|
227
|
+
let ctx = ProtectionContext::for_storage();
|
|
228
|
+
let event = StreamEvent::text_delta("id", "secret data");
|
|
229
|
+
|
|
230
|
+
let result = processor.process(event, &ctx).await.unwrap();
|
|
231
|
+
|
|
232
|
+
// Should pass through unchanged
|
|
233
|
+
assert!(!result.was_modified);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
#[tokio::test]
|
|
237
|
+
async fn test_encryption_skips_streaming_destination() {
|
|
238
|
+
let processor = EncryptionProcessor::new().enabled();
|
|
239
|
+
let ctx = ProtectionContext::for_stream(); // Not storage
|
|
240
|
+
let event = StreamEvent::text_delta("id", "secret data");
|
|
241
|
+
|
|
242
|
+
let result = processor.process(event, &ctx).await.unwrap();
|
|
243
|
+
|
|
244
|
+
// Should pass through unchanged (streaming doesn't encrypt)
|
|
245
|
+
assert!(!result.was_modified);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
#[tokio::test]
|
|
249
|
+
async fn test_encryption_encrypts_for_storage() {
|
|
250
|
+
let processor = EncryptionProcessor::new()
|
|
251
|
+
.with_key([1u8; 32])
|
|
252
|
+
.enabled();
|
|
253
|
+
let ctx = ProtectionContext::for_storage();
|
|
254
|
+
let event = StreamEvent::text_delta("id", "secret data");
|
|
255
|
+
|
|
256
|
+
let result = processor.process(event, &ctx).await.unwrap();
|
|
257
|
+
|
|
258
|
+
// Should be modified
|
|
259
|
+
assert!(result.was_modified);
|
|
260
|
+
|
|
261
|
+
// Event should show [ENCRYPTED]
|
|
262
|
+
if let StreamEvent::TextDelta { delta, .. } = result.event {
|
|
263
|
+
assert_eq!(delta, "[ENCRYPTED]");
|
|
264
|
+
} else {
|
|
265
|
+
panic!("Expected TextDelta");
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Encrypted payload should exist
|
|
269
|
+
let payload = result.encrypted_payload.expect("expected encrypted payload");
|
|
270
|
+
assert!(payload.starts_with("ENC:"));
|
|
271
|
+
assert!(
|
|
272
|
+
!payload.contains("secret data"),
|
|
273
|
+
"ciphertext should not contain plaintext"
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
#[tokio::test]
|
|
278
|
+
async fn test_encryption_control_events_pass_through() {
|
|
279
|
+
let processor = EncryptionProcessor::new().enabled();
|
|
280
|
+
let ctx = ProtectionContext::for_storage();
|
|
281
|
+
let exec_id = ExecutionId::new();
|
|
282
|
+
let event = StreamEvent::execution_start(&exec_id);
|
|
283
|
+
|
|
284
|
+
let result = processor.process(event, &ctx).await.unwrap();
|
|
285
|
+
|
|
286
|
+
// Control events should pass through unchanged
|
|
287
|
+
assert!(!result.was_modified);
|
|
288
|
+
}
|
|
289
|
+
}
|