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.
Files changed (389) hide show
  1. package/.env.example +20 -0
  2. package/.github/workflows/ci.yml +70 -0
  3. package/.github/workflows/publish.yml +250 -0
  4. package/.gitmodules +3 -0
  5. package/Cargo.lock +3584 -0
  6. package/Cargo.toml +97 -0
  7. package/crates/enact/Cargo.toml +27 -0
  8. package/crates/enact/src/lib.rs +60 -0
  9. package/crates/enact-a2a/Cargo.toml +25 -0
  10. package/crates/enact-a2a/src/lib.rs +411 -0
  11. package/crates/enact-channels/Cargo.toml +64 -0
  12. package/crates/enact-channels/examples/README.md +80 -0
  13. package/crates/enact-channels/examples/channel_bot.rs +169 -0
  14. package/crates/enact-channels/examples/telegram-echo.rs +34 -0
  15. package/crates/enact-channels/examples/whatsapp-echo.rs +142 -0
  16. package/crates/enact-channels/src/config.rs +213 -0
  17. package/crates/enact-channels/src/lib.rs +25 -0
  18. package/crates/enact-channels/src/runtime.rs +237 -0
  19. package/crates/enact-channels/src/security/mod.rs +5 -0
  20. package/crates/enact-channels/src/security/pairing.rs +205 -0
  21. package/crates/enact-channels/src/teams.rs +601 -0
  22. package/crates/enact-channels/src/telegram.rs +2833 -0
  23. package/crates/enact-channels/src/traits.rs +200 -0
  24. package/crates/enact-channels/src/webhook.rs +262 -0
  25. package/crates/enact-channels/src/whatsapp.rs +310 -0
  26. package/crates/enact-cli/Cargo.toml +40 -0
  27. package/crates/enact-cli/src/commands/doctor.rs +62 -0
  28. package/crates/enact-cli/src/commands/mod.rs +3 -0
  29. package/crates/enact-cli/src/commands/run.rs +69 -0
  30. package/crates/enact-cli/src/commands/serve.rs +81 -0
  31. package/crates/enact-cli/src/config.rs +2 -0
  32. package/crates/enact-cli/src/main.rs +79 -0
  33. package/crates/enact-config/Cargo.toml +36 -0
  34. package/crates/enact-config/ENV_VAR_MAPPING.md +135 -0
  35. package/crates/enact-config/QUICK_REFERENCE.md +92 -0
  36. package/crates/enact-config/README.md +107 -0
  37. package/crates/enact-config/TESTING.md +161 -0
  38. package/crates/enact-config/examples/test-env-vars.rs +100 -0
  39. package/crates/enact-config/src/config.rs +399 -0
  40. package/crates/enact-config/src/encrypted_store.rs +211 -0
  41. package/crates/enact-config/src/lib.rs +298 -0
  42. package/crates/enact-config/src/secrets.rs +149 -0
  43. package/crates/enact-config/src/sync.rs +260 -0
  44. package/crates/enact-config/test-env-vars.sh +34 -0
  45. package/crates/enact-config/tests/README.md +99 -0
  46. package/crates/enact-config/tests/config_integration_test.rs +202 -0
  47. package/crates/enact-config/tests/security_test.rs +140 -0
  48. package/crates/enact-context/Cargo.toml +41 -0
  49. package/crates/enact-context/src/budget.rs +314 -0
  50. package/crates/enact-context/src/calibrator.rs +535 -0
  51. package/crates/enact-context/src/compactor.rs +392 -0
  52. package/crates/enact-context/src/condenser.rs +826 -0
  53. package/crates/enact-context/src/lib.rs +94 -0
  54. package/crates/enact-context/src/segment.rs +238 -0
  55. package/crates/enact-context/src/step_context.rs +645 -0
  56. package/crates/enact-context/src/token_counter.rs +148 -0
  57. package/crates/enact-context/src/window.rs +372 -0
  58. package/crates/enact-core/Cargo.toml +42 -0
  59. package/crates/enact-core/README.md +98 -0
  60. package/crates/enact-core/src/background/executor.rs +524 -0
  61. package/crates/enact-core/src/background/mod.rs +48 -0
  62. package/crates/enact-core/src/background/target_binding.rs +390 -0
  63. package/crates/enact-core/src/background/trigger.rs +511 -0
  64. package/crates/enact-core/src/callable/callable.rs +152 -0
  65. package/crates/enact-core/src/callable/composite.rs +817 -0
  66. package/crates/enact-core/src/callable/graph.rs +104 -0
  67. package/crates/enact-core/src/callable/llm.rs +211 -0
  68. package/crates/enact-core/src/callable/mod.rs +64 -0
  69. package/crates/enact-core/src/callable/registry.rs +206 -0
  70. package/crates/enact-core/src/context/execution_context.rs +757 -0
  71. package/crates/enact-core/src/context/invocation.rs +99 -0
  72. package/crates/enact-core/src/context/mod.rs +50 -0
  73. package/crates/enact-core/src/context/tenant.rs +175 -0
  74. package/crates/enact-core/src/context/trace.rs +127 -0
  75. package/crates/enact-core/src/flow/conditional.rs +293 -0
  76. package/crates/enact-core/src/flow/mod.rs +43 -0
  77. package/crates/enact-core/src/flow/parallel.rs +437 -0
  78. package/crates/enact-core/src/flow/repeat.rs +534 -0
  79. package/crates/enact-core/src/flow/sequential.rs +248 -0
  80. package/crates/enact-core/src/graph/checkpoint.rs +79 -0
  81. package/crates/enact-core/src/graph/checkpoint_store.rs +76 -0
  82. package/crates/enact-core/src/graph/compiled.rs +189 -0
  83. package/crates/enact-core/src/graph/edge.rs +59 -0
  84. package/crates/enact-core/src/graph/graph_schema.rs +218 -0
  85. package/crates/enact-core/src/graph/loader.rs +155 -0
  86. package/crates/enact-core/src/graph/mod.rs +18 -0
  87. package/crates/enact-core/src/graph/node/function.rs +49 -0
  88. package/crates/enact-core/src/graph/node/mod.rs +48 -0
  89. package/crates/enact-core/src/graph/schema.rs +62 -0
  90. package/crates/enact-core/src/inbox/message.rs +405 -0
  91. package/crates/enact-core/src/inbox/mod.rs +31 -0
  92. package/crates/enact-core/src/inbox/store.rs +355 -0
  93. package/crates/enact-core/src/kernel/artifact/filesystem.rs +546 -0
  94. package/crates/enact-core/src/kernel/artifact/metadata.rs +283 -0
  95. package/crates/enact-core/src/kernel/artifact/mod.rs +27 -0
  96. package/crates/enact-core/src/kernel/artifact/store.rs +427 -0
  97. package/crates/enact-core/src/kernel/enforcement.rs +1315 -0
  98. package/crates/enact-core/src/kernel/error.rs +1200 -0
  99. package/crates/enact-core/src/kernel/event.rs +1394 -0
  100. package/crates/enact-core/src/kernel/execution_model.rs +831 -0
  101. package/crates/enact-core/src/kernel/execution_state.rs +189 -0
  102. package/crates/enact-core/src/kernel/execution_strategy.rs +117 -0
  103. package/crates/enact-core/src/kernel/ids.rs +2086 -0
  104. package/crates/enact-core/src/kernel/interrupt.rs +125 -0
  105. package/crates/enact-core/src/kernel/kernel.rs +1283 -0
  106. package/crates/enact-core/src/kernel/mod.rs +205 -0
  107. package/crates/enact-core/src/kernel/persistence/event_store.rs +270 -0
  108. package/crates/enact-core/src/kernel/persistence/message_store.rs +908 -0
  109. package/crates/enact-core/src/kernel/persistence/mod.rs +102 -0
  110. package/crates/enact-core/src/kernel/persistence/state_store.rs +228 -0
  111. package/crates/enact-core/src/kernel/persistence/vector_store.rs +299 -0
  112. package/crates/enact-core/src/kernel/reducer.rs +808 -0
  113. package/crates/enact-core/src/kernel/replay.rs +153 -0
  114. package/crates/enact-core/src/lib.rs +413 -0
  115. package/crates/enact-core/src/memory/episodic.rs +0 -0
  116. package/crates/enact-core/src/memory/mod.rs +6 -0
  117. package/crates/enact-core/src/memory/semantic.rs +0 -0
  118. package/crates/enact-core/src/memory/trait.rs +0 -0
  119. package/crates/enact-core/src/memory/vector_db.rs +0 -0
  120. package/crates/enact-core/src/memory/working.rs +0 -0
  121. package/crates/enact-core/src/policy/execution_policy.rs +292 -0
  122. package/crates/enact-core/src/policy/filters.rs +458 -0
  123. package/crates/enact-core/src/policy/input_processor.rs +407 -0
  124. package/crates/enact-core/src/policy/long_running.rs +134 -0
  125. package/crates/enact-core/src/policy/mod.rs +193 -0
  126. package/crates/enact-core/src/policy/pii_input.rs +274 -0
  127. package/crates/enact-core/src/policy/tenant_policy.rs +453 -0
  128. package/crates/enact-core/src/policy/tool_policy.rs +407 -0
  129. package/crates/enact-core/src/providers/mod.rs +63 -0
  130. package/crates/enact-core/src/providers/trait.rs +292 -0
  131. package/crates/enact-core/src/runner/callbacks.rs +6 -0
  132. package/crates/enact-core/src/runner/execution_runner.rs +476 -0
  133. package/crates/enact-core/src/runner/loop.rs +117 -0
  134. package/crates/enact-core/src/runner/mod.rs +58 -0
  135. package/crates/enact-core/src/runner/protected_runner.rs +280 -0
  136. package/crates/enact-core/src/signal/inmemory.rs +231 -0
  137. package/crates/enact-core/src/signal/mod.rs +108 -0
  138. package/crates/enact-core/src/streaming/event_logger.rs +195 -0
  139. package/crates/enact-core/src/streaming/event_stream.rs +1423 -0
  140. package/crates/enact-core/src/streaming/mod.rs +108 -0
  141. package/crates/enact-core/src/streaming/pause_cancel.rs +0 -0
  142. package/crates/enact-core/src/streaming/protected_emitter.rs +173 -0
  143. package/crates/enact-core/src/streaming/protection/context.rs +136 -0
  144. package/crates/enact-core/src/streaming/protection/encryption.rs +289 -0
  145. package/crates/enact-core/src/streaming/protection/mod.rs +43 -0
  146. package/crates/enact-core/src/streaming/protection/pii_protection.rs +243 -0
  147. package/crates/enact-core/src/streaming/protection/processor.rs +166 -0
  148. package/crates/enact-core/src/streaming/sse.rs +0 -0
  149. package/crates/enact-core/src/telemetry/exporter.rs +0 -0
  150. package/crates/enact-core/src/telemetry/init.rs +0 -0
  151. package/crates/enact-core/src/telemetry/mod.rs +49 -0
  152. package/crates/enact-core/src/telemetry/spans.rs +245 -0
  153. package/crates/enact-core/src/tool/agent_tool.rs +177 -0
  154. package/crates/enact-core/src/tool/browser/mod.rs +0 -0
  155. package/crates/enact-core/src/tool/browser/webdriver.rs +0 -0
  156. package/crates/enact-core/src/tool/cost.rs +247 -0
  157. package/crates/enact-core/src/tool/discovery.rs +0 -0
  158. package/crates/enact-core/src/tool/dispatcher.rs +347 -0
  159. package/crates/enact-core/src/tool/filesystem.rs +231 -0
  160. package/crates/enact-core/src/tool/function.rs +99 -0
  161. package/crates/enact-core/src/tool/git.rs +162 -0
  162. package/crates/enact-core/src/tool/http.rs +214 -0
  163. package/crates/enact-core/src/tool/mcp/client.rs +0 -0
  164. package/crates/enact-core/src/tool/mcp/mod.rs +0 -0
  165. package/crates/enact-core/src/tool/mod.rs +51 -0
  166. package/crates/enact-core/src/tool/reasoning/debugging.rs +0 -0
  167. package/crates/enact-core/src/tool/reasoning/mcts.rs +0 -0
  168. package/crates/enact-core/src/tool/reasoning/mod.rs +0 -0
  169. package/crates/enact-core/src/tool/reasoning/sequential.rs +0 -0
  170. package/crates/enact-core/src/tool/sandbox/dagger.rs +0 -0
  171. package/crates/enact-core/src/tool/sandbox/mod.rs +0 -0
  172. package/crates/enact-core/src/tool/shell.rs +147 -0
  173. package/crates/enact-core/src/tool/trait.rs +33 -0
  174. package/crates/enact-core/src/tool/web_search.rs +277 -0
  175. package/crates/enact-core/src/util/config.rs +0 -0
  176. package/crates/enact-core/src/util/errors.rs +0 -0
  177. package/crates/enact-core/src/util/mod.rs +6 -0
  178. package/crates/enact-core/tests/airgapped_e2e_test.rs +291 -0
  179. package/crates/enact-core/tests/e2e_agentic_loop.rs +119 -0
  180. package/crates/enact-core/tests/e2e_test.rs +259 -0
  181. package/crates/enact-core/tests/graph_test.rs +130 -0
  182. package/crates/enact-core/tests/stream_event_id_validation.rs +435 -0
  183. package/crates/enact-cron/Cargo.toml +28 -0
  184. package/crates/enact-cron/src/lib.rs +44 -0
  185. package/crates/enact-cron/src/schedule.rs +156 -0
  186. package/crates/enact-cron/src/store.rs +589 -0
  187. package/crates/enact-cron/src/types.rs +148 -0
  188. package/crates/enact-gateway/Cargo.toml +31 -0
  189. package/crates/enact-gateway/README.md +30 -0
  190. package/crates/enact-gateway/examples/whatsapp-gateway-runner-mock.rs +59 -0
  191. package/crates/enact-gateway/examples/whatsapp-gateway.rs +42 -0
  192. package/crates/enact-gateway/src/lib.rs +582 -0
  193. package/crates/enact-mcp/Cargo.toml +24 -0
  194. package/crates/enact-mcp/src/lib.rs +178 -0
  195. package/crates/enact-memory/Cargo.toml +25 -0
  196. package/crates/enact-memory/src/backend.rs +20 -0
  197. package/crates/enact-memory/src/chunker.rs +230 -0
  198. package/crates/enact-memory/src/embeddings.rs +221 -0
  199. package/crates/enact-memory/src/lib.rs +67 -0
  200. package/crates/enact-memory/src/markdown.rs +127 -0
  201. package/crates/enact-memory/src/none.rs +61 -0
  202. package/crates/enact-memory/src/sqlite.rs +276 -0
  203. package/crates/enact-memory/src/traits.rs +65 -0
  204. package/crates/enact-memory/src/vector.rs +198 -0
  205. package/crates/enact-oauth/Cargo.toml +27 -0
  206. package/crates/enact-oauth/src/lib.rs +584 -0
  207. package/crates/enact-observability/Cargo.toml +22 -0
  208. package/crates/enact-observability/src/lib.rs +197 -0
  209. package/crates/enact-providers/Cargo.toml +33 -0
  210. package/crates/enact-providers/examples/hello-agent.rs +33 -0
  211. package/crates/enact-providers/src/anthropic.rs +182 -0
  212. package/crates/enact-providers/src/azure.rs +96 -0
  213. package/crates/enact-providers/src/bridge.rs +221 -0
  214. package/crates/enact-providers/src/gemini.rs +227 -0
  215. package/crates/enact-providers/src/http.rs +78 -0
  216. package/crates/enact-providers/src/lib.rs +53 -0
  217. package/crates/enact-providers/src/openai_compatible.rs +167 -0
  218. package/crates/enact-providers/src/openrouter.rs +33 -0
  219. package/crates/enact-runner/Cargo.toml +24 -0
  220. package/crates/enact-runner/README.md +76 -0
  221. package/crates/enact-runner/src/compaction.rs +225 -0
  222. package/crates/enact-runner/src/config.rs +118 -0
  223. package/crates/enact-runner/src/lib.rs +63 -0
  224. package/crates/enact-runner/src/loop_driver.rs +414 -0
  225. package/crates/enact-runner/src/parser.rs +421 -0
  226. package/crates/enact-runner/src/retry.rs +262 -0
  227. package/crates/enact-runner/tests/integration.rs +278 -0
  228. package/crates/enact-security/Cargo.toml +22 -0
  229. package/crates/enact-security/src/audit.rs +375 -0
  230. package/crates/enact-security/src/lib.rs +37 -0
  231. package/crates/enact-security/src/policy.rs +406 -0
  232. package/crates/enact-skills/Cargo.toml +25 -0
  233. package/crates/enact-skills/src/lib.rs +506 -0
  234. package/crates/enact-tools/Cargo.toml +22 -0
  235. package/crates/enact-tools/src/file_read.rs +166 -0
  236. package/crates/enact-tools/src/file_write.rs +216 -0
  237. package/crates/enact-tools/src/git_operations.rs +513 -0
  238. package/crates/enact-tools/src/http_request.rs +417 -0
  239. package/crates/enact-tools/src/lib.rs +104 -0
  240. package/crates/enact-tools/src/security.rs +227 -0
  241. package/crates/enact-tools/src/shell.rs +191 -0
  242. package/crates/enact-tools/src/traits.rs +159 -0
  243. package/docs/Makefile +74 -0
  244. package/docs/config.toml +62 -0
  245. package/docs/content/_index.md +174 -0
  246. package/docs/content/a2a/_index.md +431 -0
  247. package/docs/content/api/_index.md +323 -0
  248. package/docs/content/channels/_index.md +160 -0
  249. package/docs/content/channels/teams.md +205 -0
  250. package/docs/content/channels/telegram.md +182 -0
  251. package/docs/content/channels/webhook.md +423 -0
  252. package/docs/content/channels/whatsapp.md +240 -0
  253. package/docs/content/cli/_index.md +261 -0
  254. package/docs/content/concepts/_index.md +273 -0
  255. package/docs/content/configuration/_index.md +241 -0
  256. package/docs/content/cron/_index.md +248 -0
  257. package/docs/content/developers/_index.md +278 -0
  258. package/docs/content/getting-started/_index.md +180 -0
  259. package/docs/content/installation/_index.md +186 -0
  260. package/docs/content/installation/uninstall.md +101 -0
  261. package/docs/content/installation/updating.md +120 -0
  262. package/docs/content/mcp/_index.md +215 -0
  263. package/docs/content/memory/_index.md +163 -0
  264. package/docs/content/oauth/_index.md +515 -0
  265. package/docs/content/providers/_index.md +206 -0
  266. package/docs/content/roadmap/_index.md +199 -0
  267. package/docs/content/security/_index.md +219 -0
  268. package/docs/content/skills/_index.md +228 -0
  269. package/docs/content/tools/_index.md +485 -0
  270. package/docs/content/troubleshooting/_index.md +259 -0
  271. package/docs/content/yaml-schema/_index.md +294 -0
  272. package/docs/static/giallo-dark.css +91 -0
  273. package/docs/static/giallo-light.css +91 -0
  274. package/docs/themes/tanuki/.github/workflows/deploy.yml +44 -0
  275. package/docs/themes/tanuki/LICENSE +21 -0
  276. package/docs/themes/tanuki/README.md +166 -0
  277. package/docs/themes/tanuki/examples/blog/config.toml +58 -0
  278. package/docs/themes/tanuki/examples/blog/content/_index.md +4 -0
  279. package/docs/themes/tanuki/examples/blog/content/about.md +33 -0
  280. package/docs/themes/tanuki/examples/blog/content/blog/_index.md +7 -0
  281. package/docs/themes/tanuki/examples/blog/content/blog/api-design-best-practices.md +245 -0
  282. package/docs/themes/tanuki/examples/blog/content/blog/building-accessible-websites.md +147 -0
  283. package/docs/themes/tanuki/examples/blog/content/blog/css-grid-vs-flexbox.md +165 -0
  284. package/docs/themes/tanuki/examples/blog/content/blog/customizing-catppuccin-colors.md +137 -0
  285. package/docs/themes/tanuki/examples/blog/content/blog/dark-mode-best-practices.md +82 -0
  286. package/docs/themes/tanuki/examples/blog/content/blog/docker-essentials.md +301 -0
  287. package/docs/themes/tanuki/examples/blog/content/blog/getting-started-with-zola.md +129 -0
  288. package/docs/themes/tanuki/examples/blog/content/blog/git-workflow-for-content.md +112 -0
  289. package/docs/themes/tanuki/examples/blog/content/blog/introduction-to-webassembly.md +183 -0
  290. package/docs/themes/tanuki/examples/blog/content/blog/modern-javascript-features.md +234 -0
  291. package/docs/themes/tanuki/examples/blog/content/blog/testing-strategies.md +311 -0
  292. package/docs/themes/tanuki/examples/blog/content/blog/typography-for-developers.md +104 -0
  293. package/docs/themes/tanuki/examples/blog/content/blog/welcome-to-tanuki.md +67 -0
  294. package/docs/themes/tanuki/examples/blog/content/blog/why-static-sites.md +85 -0
  295. package/docs/themes/tanuki/examples/blog/content/projects.md +64 -0
  296. package/docs/themes/tanuki/examples/book/config.toml +17 -0
  297. package/docs/themes/tanuki/examples/book/content/_index.md +12 -0
  298. package/docs/themes/tanuki/examples/book/content/chapter-1.md +90 -0
  299. package/docs/themes/tanuki/examples/book/content/chapter-2.md +143 -0
  300. package/docs/themes/tanuki/examples/book/content/chapter-3.md +217 -0
  301. package/docs/themes/tanuki/examples/book/content/chapter-4.md +224 -0
  302. package/docs/themes/tanuki/examples/book/content/chapter-5.md +297 -0
  303. package/docs/themes/tanuki/examples/book/content/print.md +6 -0
  304. package/docs/themes/tanuki/examples/docs/config.toml +28 -0
  305. package/docs/themes/tanuki/examples/docs/content/_index.md +20 -0
  306. package/docs/themes/tanuki/examples/docs/content/components.md +156 -0
  307. package/docs/themes/tanuki/examples/docs/content/configuration.md +94 -0
  308. package/docs/themes/tanuki/examples/docs/content/customization.md +202 -0
  309. package/docs/themes/tanuki/examples/docs/content/deployment.md +204 -0
  310. package/docs/themes/tanuki/examples/docs/content/installation.md +59 -0
  311. package/docs/themes/tanuki/examples/docs/content/print.md +6 -0
  312. package/docs/themes/tanuki/examples/docs/static/img/tanuki-icon.avif +0 -0
  313. package/docs/themes/tanuki/examples/index.html +2104 -0
  314. package/docs/themes/tanuki/mise.toml +108 -0
  315. package/docs/themes/tanuki/sass/base/_catppuccin.scss +164 -0
  316. package/docs/themes/tanuki/sass/base/_fonts.scss +64 -0
  317. package/docs/themes/tanuki/sass/base/_reset.scss +152 -0
  318. package/docs/themes/tanuki/sass/base/_typography.scss +523 -0
  319. package/docs/themes/tanuki/sass/components/_buttons.scss +209 -0
  320. package/docs/themes/tanuki/sass/components/_code.scss +457 -0
  321. package/docs/themes/tanuki/sass/components/_landing.scss +633 -0
  322. package/docs/themes/tanuki/sass/components/_layout.scss +294 -0
  323. package/docs/themes/tanuki/sass/components/_navigation.scss +1200 -0
  324. package/docs/themes/tanuki/sass/components/_print.scss +237 -0
  325. package/docs/themes/tanuki/sass/components/_search.scss +224 -0
  326. package/docs/themes/tanuki/sass/components/_sidebar.scss +473 -0
  327. package/docs/themes/tanuki/sass/components/_theme-toggle.scss +186 -0
  328. package/docs/themes/tanuki/sass/modes/_blog.scss +366 -0
  329. package/docs/themes/tanuki/sass/modes/_product.scss +875 -0
  330. package/docs/themes/tanuki/sass/modes/_raskell.scss +1696 -0
  331. package/docs/themes/tanuki/sass/patterns/_buttons.scss +183 -0
  332. package/docs/themes/tanuki/sass/patterns/_cards.scss +144 -0
  333. package/docs/themes/tanuki/sass/patterns/_index.scss +9 -0
  334. package/docs/themes/tanuki/sass/patterns/_lists.scss +259 -0
  335. package/docs/themes/tanuki/sass/patterns/_sections.scss +243 -0
  336. package/docs/themes/tanuki/sass/style.scss +47 -0
  337. package/docs/themes/tanuki/sass/tokens/_colors.scss +139 -0
  338. package/docs/themes/tanuki/sass/tokens/_spacing.scss +100 -0
  339. package/docs/themes/tanuki/sass/tokens/_typography.scss +186 -0
  340. package/docs/themes/tanuki/screenshot.png +0 -0
  341. package/docs/themes/tanuki/sentinel.kdl +59 -0
  342. package/docs/themes/tanuki/static/elasticlunr.min.js +10 -0
  343. package/docs/themes/tanuki/static/fonts/GEIST-LICENSE.txt +92 -0
  344. package/docs/themes/tanuki/static/fonts/Geist-Variable.woff2 +0 -0
  345. package/docs/themes/tanuki/static/fonts/GeistMono-Variable.woff2 +0 -0
  346. package/docs/themes/tanuki/static/img/tanuki-icon.avif +0 -0
  347. package/docs/themes/tanuki/static/img/tanuki-icon.png +0 -0
  348. package/docs/themes/tanuki/static/js/anchors.js +18 -0
  349. package/docs/themes/tanuki/static/js/app.js +274 -0
  350. package/docs/themes/tanuki/static/js/code.js +394 -0
  351. package/docs/themes/tanuki/static/js/navigation.js +778 -0
  352. package/docs/themes/tanuki/static/js/scroll-to-top.js +33 -0
  353. package/docs/themes/tanuki/static/js/search-raskell.js +240 -0
  354. package/docs/themes/tanuki/static/js/search.js +215 -0
  355. package/docs/themes/tanuki/static/js/theme.js +169 -0
  356. package/docs/themes/tanuki/static/syntax-dark.css +151 -0
  357. package/docs/themes/tanuki/static/syntax-light.css +151 -0
  358. package/docs/themes/tanuki/static/wasm/sentinel_playground_wasm.js +486 -0
  359. package/docs/themes/tanuki/static/wasm/sentinel_playground_wasm_bg.wasm +0 -0
  360. package/docs/themes/tanuki/templates/404.html +52 -0
  361. package/docs/themes/tanuki/templates/base.html +428 -0
  362. package/docs/themes/tanuki/templates/blog.html +66 -0
  363. package/docs/themes/tanuki/templates/home.html +108 -0
  364. package/docs/themes/tanuki/templates/index.html +178 -0
  365. package/docs/themes/tanuki/templates/landing.html +168 -0
  366. package/docs/themes/tanuki/templates/macros/nav.html +128 -0
  367. package/docs/themes/tanuki/templates/macros/posts.html +101 -0
  368. package/docs/themes/tanuki/templates/macros/ui.html +159 -0
  369. package/docs/themes/tanuki/templates/page.html +135 -0
  370. package/docs/themes/tanuki/templates/partials/footer.html +38 -0
  371. package/docs/themes/tanuki/templates/partials/header.html +366 -0
  372. package/docs/themes/tanuki/templates/partials/nav-buttons.html +55 -0
  373. package/docs/themes/tanuki/templates/partials/nav-overlay.html +81 -0
  374. package/docs/themes/tanuki/templates/partials/page-toc-panel.html +43 -0
  375. package/docs/themes/tanuki/templates/partials/search.html +52 -0
  376. package/docs/themes/tanuki/templates/partials/sidebar.html +107 -0
  377. package/docs/themes/tanuki/templates/partials/theme-toggle.html +35 -0
  378. package/docs/themes/tanuki/templates/partials/toc-overlay.html +146 -0
  379. package/docs/themes/tanuki/templates/partials/version-picker.html +38 -0
  380. package/docs/themes/tanuki/templates/print.html +244 -0
  381. package/docs/themes/tanuki/templates/section.html +186 -0
  382. package/docs/themes/tanuki/templates/taxonomy_list.html +18 -0
  383. package/docs/themes/tanuki/templates/taxonomy_single.html +31 -0
  384. package/docs/themes/tanuki/theme.toml +58 -0
  385. package/examples/hello-agent.rs +55 -0
  386. package/package.json +36 -0
  387. package/proto/config.proto +60 -0
  388. package/proto/events.proto +0 -0
  389. 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
+ };
@@ -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
+ }