crca 1.4.0__py3-none-any.whl

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 (501) hide show
  1. .github/ISSUE_TEMPLATE/bug_report.md +65 -0
  2. .github/ISSUE_TEMPLATE/feature_request.md +41 -0
  3. .github/PULL_REQUEST_TEMPLATE.md +20 -0
  4. .github/workflows/publish-manual.yml +61 -0
  5. .github/workflows/publish.yml +64 -0
  6. .gitignore +214 -0
  7. CRCA.py +4156 -0
  8. LICENSE +201 -0
  9. MANIFEST.in +43 -0
  10. PKG-INFO +5035 -0
  11. README.md +4959 -0
  12. __init__.py +17 -0
  13. branches/CRCA-Q.py +2728 -0
  14. branches/crca_cg/corposwarm.py +9065 -0
  15. branches/crca_cg/fix_rancher_docker_creds.ps1 +155 -0
  16. branches/crca_cg/package.json +5 -0
  17. branches/crca_cg/test_bolt_integration.py +446 -0
  18. branches/crca_cg/test_corposwarm_comprehensive.py +773 -0
  19. branches/crca_cg/test_new_features.py +163 -0
  20. branches/crca_sd/__init__.py +149 -0
  21. branches/crca_sd/crca_sd_core.py +770 -0
  22. branches/crca_sd/crca_sd_governance.py +1325 -0
  23. branches/crca_sd/crca_sd_mpc.py +1130 -0
  24. branches/crca_sd/crca_sd_realtime.py +1844 -0
  25. branches/crca_sd/crca_sd_tui.py +1133 -0
  26. crca-1.4.0.dist-info/METADATA +5035 -0
  27. crca-1.4.0.dist-info/RECORD +501 -0
  28. crca-1.4.0.dist-info/WHEEL +4 -0
  29. crca-1.4.0.dist-info/licenses/LICENSE +201 -0
  30. docs/CRCA-Q.md +2333 -0
  31. examples/config.yaml.example +25 -0
  32. examples/crca_sd_example.py +513 -0
  33. examples/data_broker_example.py +294 -0
  34. examples/logistics_corporation.py +861 -0
  35. examples/palantir_example.py +299 -0
  36. examples/policy_bench.py +934 -0
  37. examples/pridnestrovia-sd.py +705 -0
  38. examples/pridnestrovia_realtime.py +1902 -0
  39. prompts/__init__.py +10 -0
  40. prompts/default_crca.py +101 -0
  41. pyproject.toml +151 -0
  42. requirements.txt +76 -0
  43. schemas/__init__.py +43 -0
  44. schemas/mcpSchemas.py +51 -0
  45. schemas/policy.py +458 -0
  46. templates/__init__.py +38 -0
  47. templates/base_specialized_agent.py +195 -0
  48. templates/drift_detection.py +325 -0
  49. templates/examples/causal_agent_template.py +309 -0
  50. templates/examples/drag_drop_example.py +213 -0
  51. templates/examples/logistics_agent_template.py +207 -0
  52. templates/examples/trading_agent_template.py +206 -0
  53. templates/feature_mixins.py +253 -0
  54. templates/graph_management.py +442 -0
  55. templates/llm_integration.py +194 -0
  56. templates/module_registry.py +276 -0
  57. templates/mpc_planner.py +280 -0
  58. templates/policy_loop.py +1168 -0
  59. templates/prediction_framework.py +448 -0
  60. templates/statistical_methods.py +778 -0
  61. tests/sanity.yml +31 -0
  62. tests/sanity_check +406 -0
  63. tests/test_core.py +47 -0
  64. tests/test_crca_excel.py +166 -0
  65. tests/test_crca_sd.py +780 -0
  66. tests/test_data_broker.py +424 -0
  67. tests/test_palantir.py +349 -0
  68. tools/__init__.py +38 -0
  69. tools/actuators.py +437 -0
  70. tools/bolt.diy/Dockerfile +103 -0
  71. tools/bolt.diy/app/components/@settings/core/AvatarDropdown.tsx +175 -0
  72. tools/bolt.diy/app/components/@settings/core/ControlPanel.tsx +345 -0
  73. tools/bolt.diy/app/components/@settings/core/constants.tsx +108 -0
  74. tools/bolt.diy/app/components/@settings/core/types.ts +114 -0
  75. tools/bolt.diy/app/components/@settings/index.ts +12 -0
  76. tools/bolt.diy/app/components/@settings/shared/components/TabTile.tsx +151 -0
  77. tools/bolt.diy/app/components/@settings/shared/service-integration/ConnectionForm.tsx +193 -0
  78. tools/bolt.diy/app/components/@settings/shared/service-integration/ConnectionTestIndicator.tsx +60 -0
  79. tools/bolt.diy/app/components/@settings/shared/service-integration/ErrorState.tsx +102 -0
  80. tools/bolt.diy/app/components/@settings/shared/service-integration/LoadingState.tsx +94 -0
  81. tools/bolt.diy/app/components/@settings/shared/service-integration/ServiceHeader.tsx +72 -0
  82. tools/bolt.diy/app/components/@settings/shared/service-integration/index.ts +6 -0
  83. tools/bolt.diy/app/components/@settings/tabs/data/DataTab.tsx +721 -0
  84. tools/bolt.diy/app/components/@settings/tabs/data/DataVisualization.tsx +384 -0
  85. tools/bolt.diy/app/components/@settings/tabs/event-logs/EventLogsTab.tsx +1013 -0
  86. tools/bolt.diy/app/components/@settings/tabs/features/FeaturesTab.tsx +295 -0
  87. tools/bolt.diy/app/components/@settings/tabs/github/GitHubTab.tsx +281 -0
  88. tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubAuthDialog.tsx +173 -0
  89. tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubCacheManager.tsx +367 -0
  90. tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubConnection.tsx +233 -0
  91. tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubErrorBoundary.tsx +105 -0
  92. tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubProgressiveLoader.tsx +266 -0
  93. tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubRepositoryCard.tsx +121 -0
  94. tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubRepositorySelector.tsx +312 -0
  95. tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubStats.tsx +291 -0
  96. tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubUserProfile.tsx +46 -0
  97. tools/bolt.diy/app/components/@settings/tabs/github/components/shared/GitHubStateIndicators.tsx +264 -0
  98. tools/bolt.diy/app/components/@settings/tabs/github/components/shared/RepositoryCard.tsx +361 -0
  99. tools/bolt.diy/app/components/@settings/tabs/github/components/shared/index.ts +11 -0
  100. tools/bolt.diy/app/components/@settings/tabs/gitlab/GitLabTab.tsx +305 -0
  101. tools/bolt.diy/app/components/@settings/tabs/gitlab/components/GitLabAuthDialog.tsx +186 -0
  102. tools/bolt.diy/app/components/@settings/tabs/gitlab/components/GitLabConnection.tsx +253 -0
  103. tools/bolt.diy/app/components/@settings/tabs/gitlab/components/GitLabRepositorySelector.tsx +358 -0
  104. tools/bolt.diy/app/components/@settings/tabs/gitlab/components/RepositoryCard.tsx +79 -0
  105. tools/bolt.diy/app/components/@settings/tabs/gitlab/components/RepositoryList.tsx +142 -0
  106. tools/bolt.diy/app/components/@settings/tabs/gitlab/components/StatsDisplay.tsx +91 -0
  107. tools/bolt.diy/app/components/@settings/tabs/gitlab/components/index.ts +4 -0
  108. tools/bolt.diy/app/components/@settings/tabs/mcp/McpServerList.tsx +99 -0
  109. tools/bolt.diy/app/components/@settings/tabs/mcp/McpServerListItem.tsx +70 -0
  110. tools/bolt.diy/app/components/@settings/tabs/mcp/McpStatusBadge.tsx +37 -0
  111. tools/bolt.diy/app/components/@settings/tabs/mcp/McpTab.tsx +239 -0
  112. tools/bolt.diy/app/components/@settings/tabs/netlify/NetlifyTab.tsx +1393 -0
  113. tools/bolt.diy/app/components/@settings/tabs/netlify/components/NetlifyConnection.tsx +990 -0
  114. tools/bolt.diy/app/components/@settings/tabs/netlify/components/index.ts +1 -0
  115. tools/bolt.diy/app/components/@settings/tabs/notifications/NotificationsTab.tsx +300 -0
  116. tools/bolt.diy/app/components/@settings/tabs/profile/ProfileTab.tsx +181 -0
  117. tools/bolt.diy/app/components/@settings/tabs/providers/cloud/CloudProvidersTab.tsx +308 -0
  118. tools/bolt.diy/app/components/@settings/tabs/providers/local/ErrorBoundary.tsx +68 -0
  119. tools/bolt.diy/app/components/@settings/tabs/providers/local/HealthStatusBadge.tsx +64 -0
  120. tools/bolt.diy/app/components/@settings/tabs/providers/local/LoadingSkeleton.tsx +107 -0
  121. tools/bolt.diy/app/components/@settings/tabs/providers/local/LocalProvidersTab.tsx +556 -0
  122. tools/bolt.diy/app/components/@settings/tabs/providers/local/ModelCard.tsx +106 -0
  123. tools/bolt.diy/app/components/@settings/tabs/providers/local/ProviderCard.tsx +120 -0
  124. tools/bolt.diy/app/components/@settings/tabs/providers/local/SetupGuide.tsx +671 -0
  125. tools/bolt.diy/app/components/@settings/tabs/providers/local/StatusDashboard.tsx +91 -0
  126. tools/bolt.diy/app/components/@settings/tabs/providers/local/types.ts +44 -0
  127. tools/bolt.diy/app/components/@settings/tabs/settings/SettingsTab.tsx +215 -0
  128. tools/bolt.diy/app/components/@settings/tabs/supabase/SupabaseTab.tsx +1089 -0
  129. tools/bolt.diy/app/components/@settings/tabs/vercel/VercelTab.tsx +909 -0
  130. tools/bolt.diy/app/components/@settings/tabs/vercel/components/VercelConnection.tsx +368 -0
  131. tools/bolt.diy/app/components/@settings/tabs/vercel/components/index.ts +1 -0
  132. tools/bolt.diy/app/components/@settings/utils/tab-helpers.ts +54 -0
  133. tools/bolt.diy/app/components/chat/APIKeyManager.tsx +169 -0
  134. tools/bolt.diy/app/components/chat/Artifact.tsx +296 -0
  135. tools/bolt.diy/app/components/chat/AssistantMessage.tsx +192 -0
  136. tools/bolt.diy/app/components/chat/BaseChat.module.scss +47 -0
  137. tools/bolt.diy/app/components/chat/BaseChat.tsx +522 -0
  138. tools/bolt.diy/app/components/chat/Chat.client.tsx +670 -0
  139. tools/bolt.diy/app/components/chat/ChatAlert.tsx +108 -0
  140. tools/bolt.diy/app/components/chat/ChatBox.tsx +334 -0
  141. tools/bolt.diy/app/components/chat/CodeBlock.module.scss +10 -0
  142. tools/bolt.diy/app/components/chat/CodeBlock.tsx +85 -0
  143. tools/bolt.diy/app/components/chat/DicussMode.tsx +17 -0
  144. tools/bolt.diy/app/components/chat/ExamplePrompts.tsx +37 -0
  145. tools/bolt.diy/app/components/chat/FilePreview.tsx +38 -0
  146. tools/bolt.diy/app/components/chat/GitCloneButton.tsx +327 -0
  147. tools/bolt.diy/app/components/chat/ImportFolderButton.tsx +141 -0
  148. tools/bolt.diy/app/components/chat/LLMApiAlert.tsx +109 -0
  149. tools/bolt.diy/app/components/chat/MCPTools.tsx +129 -0
  150. tools/bolt.diy/app/components/chat/Markdown.module.scss +171 -0
  151. tools/bolt.diy/app/components/chat/Markdown.spec.ts +48 -0
  152. tools/bolt.diy/app/components/chat/Markdown.tsx +252 -0
  153. tools/bolt.diy/app/components/chat/Messages.client.tsx +102 -0
  154. tools/bolt.diy/app/components/chat/ModelSelector.tsx +797 -0
  155. tools/bolt.diy/app/components/chat/NetlifyDeploymentLink.client.tsx +51 -0
  156. tools/bolt.diy/app/components/chat/ProgressCompilation.tsx +110 -0
  157. tools/bolt.diy/app/components/chat/ScreenshotStateManager.tsx +33 -0
  158. tools/bolt.diy/app/components/chat/SendButton.client.tsx +39 -0
  159. tools/bolt.diy/app/components/chat/SpeechRecognition.tsx +28 -0
  160. tools/bolt.diy/app/components/chat/StarterTemplates.tsx +38 -0
  161. tools/bolt.diy/app/components/chat/SupabaseAlert.tsx +199 -0
  162. tools/bolt.diy/app/components/chat/SupabaseConnection.tsx +339 -0
  163. tools/bolt.diy/app/components/chat/ThoughtBox.tsx +43 -0
  164. tools/bolt.diy/app/components/chat/ToolInvocations.tsx +409 -0
  165. tools/bolt.diy/app/components/chat/UserMessage.tsx +101 -0
  166. tools/bolt.diy/app/components/chat/VercelDeploymentLink.client.tsx +158 -0
  167. tools/bolt.diy/app/components/chat/chatExportAndImport/ExportChatButton.tsx +49 -0
  168. tools/bolt.diy/app/components/chat/chatExportAndImport/ImportButtons.tsx +96 -0
  169. tools/bolt.diy/app/components/deploy/DeployAlert.tsx +197 -0
  170. tools/bolt.diy/app/components/deploy/DeployButton.tsx +277 -0
  171. tools/bolt.diy/app/components/deploy/GitHubDeploy.client.tsx +171 -0
  172. tools/bolt.diy/app/components/deploy/GitHubDeploymentDialog.tsx +1041 -0
  173. tools/bolt.diy/app/components/deploy/GitLabDeploy.client.tsx +171 -0
  174. tools/bolt.diy/app/components/deploy/GitLabDeploymentDialog.tsx +764 -0
  175. tools/bolt.diy/app/components/deploy/NetlifyDeploy.client.tsx +246 -0
  176. tools/bolt.diy/app/components/deploy/VercelDeploy.client.tsx +235 -0
  177. tools/bolt.diy/app/components/editor/codemirror/BinaryContent.tsx +7 -0
  178. tools/bolt.diy/app/components/editor/codemirror/CodeMirrorEditor.tsx +555 -0
  179. tools/bolt.diy/app/components/editor/codemirror/EnvMasking.ts +80 -0
  180. tools/bolt.diy/app/components/editor/codemirror/cm-theme.ts +192 -0
  181. tools/bolt.diy/app/components/editor/codemirror/indent.ts +68 -0
  182. tools/bolt.diy/app/components/editor/codemirror/languages.ts +112 -0
  183. tools/bolt.diy/app/components/git/GitUrlImport.client.tsx +147 -0
  184. tools/bolt.diy/app/components/header/Header.tsx +42 -0
  185. tools/bolt.diy/app/components/header/HeaderActionButtons.client.tsx +54 -0
  186. tools/bolt.diy/app/components/mandate/MandateSubmission.tsx +167 -0
  187. tools/bolt.diy/app/components/observability/DeploymentStatus.tsx +168 -0
  188. tools/bolt.diy/app/components/observability/EventTimeline.tsx +119 -0
  189. tools/bolt.diy/app/components/observability/FileDiffViewer.tsx +121 -0
  190. tools/bolt.diy/app/components/observability/GovernanceStatus.tsx +197 -0
  191. tools/bolt.diy/app/components/observability/GovernorMetrics.tsx +246 -0
  192. tools/bolt.diy/app/components/observability/LogStream.tsx +244 -0
  193. tools/bolt.diy/app/components/observability/MandateDetails.tsx +201 -0
  194. tools/bolt.diy/app/components/observability/ObservabilityDashboard.tsx +200 -0
  195. tools/bolt.diy/app/components/sidebar/HistoryItem.tsx +187 -0
  196. tools/bolt.diy/app/components/sidebar/Menu.client.tsx +536 -0
  197. tools/bolt.diy/app/components/sidebar/date-binning.ts +59 -0
  198. tools/bolt.diy/app/components/txt +1 -0
  199. tools/bolt.diy/app/components/ui/BackgroundRays/index.tsx +18 -0
  200. tools/bolt.diy/app/components/ui/BackgroundRays/styles.module.scss +246 -0
  201. tools/bolt.diy/app/components/ui/Badge.tsx +53 -0
  202. tools/bolt.diy/app/components/ui/BranchSelector.tsx +270 -0
  203. tools/bolt.diy/app/components/ui/Breadcrumbs.tsx +101 -0
  204. tools/bolt.diy/app/components/ui/Button.tsx +46 -0
  205. tools/bolt.diy/app/components/ui/Card.tsx +55 -0
  206. tools/bolt.diy/app/components/ui/Checkbox.tsx +32 -0
  207. tools/bolt.diy/app/components/ui/CloseButton.tsx +49 -0
  208. tools/bolt.diy/app/components/ui/CodeBlock.tsx +103 -0
  209. tools/bolt.diy/app/components/ui/Collapsible.tsx +9 -0
  210. tools/bolt.diy/app/components/ui/ColorSchemeDialog.tsx +378 -0
  211. tools/bolt.diy/app/components/ui/Dialog.tsx +449 -0
  212. tools/bolt.diy/app/components/ui/Dropdown.tsx +63 -0
  213. tools/bolt.diy/app/components/ui/EmptyState.tsx +154 -0
  214. tools/bolt.diy/app/components/ui/FileIcon.tsx +346 -0
  215. tools/bolt.diy/app/components/ui/FilterChip.tsx +92 -0
  216. tools/bolt.diy/app/components/ui/GlowingEffect.tsx +192 -0
  217. tools/bolt.diy/app/components/ui/GradientCard.tsx +100 -0
  218. tools/bolt.diy/app/components/ui/IconButton.tsx +84 -0
  219. tools/bolt.diy/app/components/ui/Input.tsx +22 -0
  220. tools/bolt.diy/app/components/ui/Label.tsx +20 -0
  221. tools/bolt.diy/app/components/ui/LoadingDots.tsx +27 -0
  222. tools/bolt.diy/app/components/ui/LoadingOverlay.tsx +32 -0
  223. tools/bolt.diy/app/components/ui/PanelHeader.tsx +20 -0
  224. tools/bolt.diy/app/components/ui/PanelHeaderButton.tsx +36 -0
  225. tools/bolt.diy/app/components/ui/Popover.tsx +29 -0
  226. tools/bolt.diy/app/components/ui/Progress.tsx +22 -0
  227. tools/bolt.diy/app/components/ui/RepositoryStats.tsx +87 -0
  228. tools/bolt.diy/app/components/ui/ScrollArea.tsx +41 -0
  229. tools/bolt.diy/app/components/ui/SearchInput.tsx +80 -0
  230. tools/bolt.diy/app/components/ui/SearchResultItem.tsx +134 -0
  231. tools/bolt.diy/app/components/ui/Separator.tsx +22 -0
  232. tools/bolt.diy/app/components/ui/SettingsButton.tsx +35 -0
  233. tools/bolt.diy/app/components/ui/Slider.tsx +73 -0
  234. tools/bolt.diy/app/components/ui/StatusIndicator.tsx +90 -0
  235. tools/bolt.diy/app/components/ui/Switch.tsx +37 -0
  236. tools/bolt.diy/app/components/ui/Tabs.tsx +52 -0
  237. tools/bolt.diy/app/components/ui/TabsWithSlider.tsx +112 -0
  238. tools/bolt.diy/app/components/ui/ThemeSwitch.tsx +29 -0
  239. tools/bolt.diy/app/components/ui/Tooltip.tsx +122 -0
  240. tools/bolt.diy/app/components/ui/index.ts +38 -0
  241. tools/bolt.diy/app/components/ui/use-toast.ts +66 -0
  242. tools/bolt.diy/app/components/workbench/DiffView.tsx +796 -0
  243. tools/bolt.diy/app/components/workbench/EditorPanel.tsx +174 -0
  244. tools/bolt.diy/app/components/workbench/ExpoQrModal.tsx +55 -0
  245. tools/bolt.diy/app/components/workbench/FileBreadcrumb.tsx +150 -0
  246. tools/bolt.diy/app/components/workbench/FileTree.tsx +565 -0
  247. tools/bolt.diy/app/components/workbench/Inspector.tsx +126 -0
  248. tools/bolt.diy/app/components/workbench/InspectorPanel.tsx +146 -0
  249. tools/bolt.diy/app/components/workbench/LockManager.tsx +262 -0
  250. tools/bolt.diy/app/components/workbench/PortDropdown.tsx +91 -0
  251. tools/bolt.diy/app/components/workbench/Preview.tsx +1049 -0
  252. tools/bolt.diy/app/components/workbench/ScreenshotSelector.tsx +293 -0
  253. tools/bolt.diy/app/components/workbench/Search.tsx +257 -0
  254. tools/bolt.diy/app/components/workbench/Workbench.client.tsx +506 -0
  255. tools/bolt.diy/app/components/workbench/terminal/Terminal.tsx +131 -0
  256. tools/bolt.diy/app/components/workbench/terminal/TerminalManager.tsx +68 -0
  257. tools/bolt.diy/app/components/workbench/terminal/TerminalTabs.tsx +277 -0
  258. tools/bolt.diy/app/components/workbench/terminal/theme.ts +36 -0
  259. tools/bolt.diy/app/components/workflow/WorkflowPhase.tsx +109 -0
  260. tools/bolt.diy/app/components/workflow/WorkflowStatus.tsx +60 -0
  261. tools/bolt.diy/app/components/workflow/WorkflowTimeline.tsx +150 -0
  262. tools/bolt.diy/app/entry.client.tsx +7 -0
  263. tools/bolt.diy/app/entry.server.tsx +80 -0
  264. tools/bolt.diy/app/root.tsx +156 -0
  265. tools/bolt.diy/app/routes/_index.tsx +175 -0
  266. tools/bolt.diy/app/routes/api.bug-report.ts +254 -0
  267. tools/bolt.diy/app/routes/api.chat.ts +463 -0
  268. tools/bolt.diy/app/routes/api.check-env-key.ts +41 -0
  269. tools/bolt.diy/app/routes/api.configured-providers.ts +110 -0
  270. tools/bolt.diy/app/routes/api.corporate-swarm-status.ts +55 -0
  271. tools/bolt.diy/app/routes/api.enhancer.ts +137 -0
  272. tools/bolt.diy/app/routes/api.export-api-keys.ts +44 -0
  273. tools/bolt.diy/app/routes/api.git-info.ts +69 -0
  274. tools/bolt.diy/app/routes/api.git-proxy.$.ts +178 -0
  275. tools/bolt.diy/app/routes/api.github-branches.ts +166 -0
  276. tools/bolt.diy/app/routes/api.github-deploy.ts +67 -0
  277. tools/bolt.diy/app/routes/api.github-stats.ts +198 -0
  278. tools/bolt.diy/app/routes/api.github-template.ts +242 -0
  279. tools/bolt.diy/app/routes/api.github-user.ts +287 -0
  280. tools/bolt.diy/app/routes/api.gitlab-branches.ts +143 -0
  281. tools/bolt.diy/app/routes/api.gitlab-deploy.ts +67 -0
  282. tools/bolt.diy/app/routes/api.gitlab-projects.ts +105 -0
  283. tools/bolt.diy/app/routes/api.health.ts +8 -0
  284. tools/bolt.diy/app/routes/api.llmcall.ts +298 -0
  285. tools/bolt.diy/app/routes/api.mandate.ts +351 -0
  286. tools/bolt.diy/app/routes/api.mcp-check.ts +16 -0
  287. tools/bolt.diy/app/routes/api.mcp-update-config.ts +23 -0
  288. tools/bolt.diy/app/routes/api.models.$provider.ts +2 -0
  289. tools/bolt.diy/app/routes/api.models.ts +90 -0
  290. tools/bolt.diy/app/routes/api.netlify-deploy.ts +240 -0
  291. tools/bolt.diy/app/routes/api.netlify-user.ts +142 -0
  292. tools/bolt.diy/app/routes/api.supabase-user.ts +199 -0
  293. tools/bolt.diy/app/routes/api.supabase.query.ts +92 -0
  294. tools/bolt.diy/app/routes/api.supabase.ts +56 -0
  295. tools/bolt.diy/app/routes/api.supabase.variables.ts +32 -0
  296. tools/bolt.diy/app/routes/api.system.diagnostics.ts +142 -0
  297. tools/bolt.diy/app/routes/api.system.disk-info.ts +311 -0
  298. tools/bolt.diy/app/routes/api.system.git-info.ts +332 -0
  299. tools/bolt.diy/app/routes/api.update.ts +21 -0
  300. tools/bolt.diy/app/routes/api.vercel-deploy.ts +497 -0
  301. tools/bolt.diy/app/routes/api.vercel-user.ts +161 -0
  302. tools/bolt.diy/app/routes/api.workflow-status.$proposalId.ts +309 -0
  303. tools/bolt.diy/app/routes/chat.$id.tsx +8 -0
  304. tools/bolt.diy/app/routes/execute.$mandateId.tsx +432 -0
  305. tools/bolt.diy/app/routes/git.tsx +25 -0
  306. tools/bolt.diy/app/routes/observability.$mandateId.tsx +50 -0
  307. tools/bolt.diy/app/routes/webcontainer.connect.$id.tsx +32 -0
  308. tools/bolt.diy/app/routes/webcontainer.preview.$id.tsx +97 -0
  309. tools/bolt.diy/app/routes/workflow.$proposalId.tsx +170 -0
  310. tools/bolt.diy/app/styles/animations.scss +49 -0
  311. tools/bolt.diy/app/styles/components/code.scss +9 -0
  312. tools/bolt.diy/app/styles/components/editor.scss +135 -0
  313. tools/bolt.diy/app/styles/components/resize-handle.scss +30 -0
  314. tools/bolt.diy/app/styles/components/terminal.scss +3 -0
  315. tools/bolt.diy/app/styles/components/toast.scss +23 -0
  316. tools/bolt.diy/app/styles/diff-view.css +72 -0
  317. tools/bolt.diy/app/styles/index.scss +73 -0
  318. tools/bolt.diy/app/styles/variables.scss +255 -0
  319. tools/bolt.diy/app/styles/z-index.scss +37 -0
  320. tools/bolt.diy/app/types/GitHub.ts +182 -0
  321. tools/bolt.diy/app/types/GitLab.ts +103 -0
  322. tools/bolt.diy/app/types/actions.ts +85 -0
  323. tools/bolt.diy/app/types/artifact.ts +5 -0
  324. tools/bolt.diy/app/types/context.ts +26 -0
  325. tools/bolt.diy/app/types/design-scheme.ts +93 -0
  326. tools/bolt.diy/app/types/global.d.ts +13 -0
  327. tools/bolt.diy/app/types/mandate.ts +333 -0
  328. tools/bolt.diy/app/types/model.ts +25 -0
  329. tools/bolt.diy/app/types/netlify.ts +94 -0
  330. tools/bolt.diy/app/types/supabase.ts +54 -0
  331. tools/bolt.diy/app/types/template.ts +8 -0
  332. tools/bolt.diy/app/types/terminal.ts +9 -0
  333. tools/bolt.diy/app/types/theme.ts +1 -0
  334. tools/bolt.diy/app/types/vercel.ts +67 -0
  335. tools/bolt.diy/app/utils/buffer.ts +29 -0
  336. tools/bolt.diy/app/utils/classNames.ts +65 -0
  337. tools/bolt.diy/app/utils/constants.ts +147 -0
  338. tools/bolt.diy/app/utils/debounce.ts +13 -0
  339. tools/bolt.diy/app/utils/debugLogger.ts +1284 -0
  340. tools/bolt.diy/app/utils/diff.spec.ts +11 -0
  341. tools/bolt.diy/app/utils/diff.ts +117 -0
  342. tools/bolt.diy/app/utils/easings.ts +3 -0
  343. tools/bolt.diy/app/utils/fileLocks.ts +96 -0
  344. tools/bolt.diy/app/utils/fileUtils.ts +121 -0
  345. tools/bolt.diy/app/utils/folderImport.ts +73 -0
  346. tools/bolt.diy/app/utils/formatSize.ts +12 -0
  347. tools/bolt.diy/app/utils/getLanguageFromExtension.ts +24 -0
  348. tools/bolt.diy/app/utils/githubStats.ts +9 -0
  349. tools/bolt.diy/app/utils/gitlabStats.ts +54 -0
  350. tools/bolt.diy/app/utils/logger.ts +162 -0
  351. tools/bolt.diy/app/utils/markdown.ts +155 -0
  352. tools/bolt.diy/app/utils/mobile.ts +4 -0
  353. tools/bolt.diy/app/utils/os.ts +4 -0
  354. tools/bolt.diy/app/utils/path.ts +19 -0
  355. tools/bolt.diy/app/utils/projectCommands.ts +197 -0
  356. tools/bolt.diy/app/utils/promises.ts +19 -0
  357. tools/bolt.diy/app/utils/react.ts +6 -0
  358. tools/bolt.diy/app/utils/sampler.ts +49 -0
  359. tools/bolt.diy/app/utils/selectStarterTemplate.ts +255 -0
  360. tools/bolt.diy/app/utils/shell.ts +384 -0
  361. tools/bolt.diy/app/utils/stacktrace.ts +27 -0
  362. tools/bolt.diy/app/utils/stripIndent.ts +23 -0
  363. tools/bolt.diy/app/utils/terminal.ts +11 -0
  364. tools/bolt.diy/app/utils/unreachable.ts +3 -0
  365. tools/bolt.diy/app/vite-env.d.ts +2 -0
  366. tools/bolt.diy/assets/entitlements.mac.plist +25 -0
  367. tools/bolt.diy/assets/icons/icon.icns +0 -0
  368. tools/bolt.diy/assets/icons/icon.ico +0 -0
  369. tools/bolt.diy/assets/icons/icon.png +0 -0
  370. tools/bolt.diy/bindings.js +78 -0
  371. tools/bolt.diy/bindings.sh +33 -0
  372. tools/bolt.diy/docker-compose.yaml +145 -0
  373. tools/bolt.diy/electron/main/index.ts +201 -0
  374. tools/bolt.diy/electron/main/tsconfig.json +30 -0
  375. tools/bolt.diy/electron/main/ui/menu.ts +29 -0
  376. tools/bolt.diy/electron/main/ui/window.ts +54 -0
  377. tools/bolt.diy/electron/main/utils/auto-update.ts +110 -0
  378. tools/bolt.diy/electron/main/utils/constants.ts +4 -0
  379. tools/bolt.diy/electron/main/utils/cookie.ts +40 -0
  380. tools/bolt.diy/electron/main/utils/reload.ts +35 -0
  381. tools/bolt.diy/electron/main/utils/serve.ts +71 -0
  382. tools/bolt.diy/electron/main/utils/store.ts +3 -0
  383. tools/bolt.diy/electron/main/utils/vite-server.ts +44 -0
  384. tools/bolt.diy/electron/main/vite.config.ts +44 -0
  385. tools/bolt.diy/electron/preload/index.ts +22 -0
  386. tools/bolt.diy/electron/preload/tsconfig.json +7 -0
  387. tools/bolt.diy/electron/preload/vite.config.ts +31 -0
  388. tools/bolt.diy/electron-builder.yml +64 -0
  389. tools/bolt.diy/electron-update.yml +4 -0
  390. tools/bolt.diy/eslint.config.mjs +57 -0
  391. tools/bolt.diy/functions/[[path]].ts +12 -0
  392. tools/bolt.diy/icons/angular.svg +1 -0
  393. tools/bolt.diy/icons/astro.svg +8 -0
  394. tools/bolt.diy/icons/chat.svg +1 -0
  395. tools/bolt.diy/icons/expo-brand.svg +1 -0
  396. tools/bolt.diy/icons/expo.svg +4 -0
  397. tools/bolt.diy/icons/logo-text.svg +1 -0
  398. tools/bolt.diy/icons/logo.svg +4 -0
  399. tools/bolt.diy/icons/mcp.svg +1 -0
  400. tools/bolt.diy/icons/nativescript.svg +1 -0
  401. tools/bolt.diy/icons/netlify.svg +10 -0
  402. tools/bolt.diy/icons/nextjs.svg +1 -0
  403. tools/bolt.diy/icons/nuxt.svg +1 -0
  404. tools/bolt.diy/icons/qwik.svg +1 -0
  405. tools/bolt.diy/icons/react.svg +1 -0
  406. tools/bolt.diy/icons/remix.svg +24 -0
  407. tools/bolt.diy/icons/remotion.svg +1 -0
  408. tools/bolt.diy/icons/shadcn.svg +21 -0
  409. tools/bolt.diy/icons/slidev.svg +60 -0
  410. tools/bolt.diy/icons/solidjs.svg +1 -0
  411. tools/bolt.diy/icons/stars.svg +1 -0
  412. tools/bolt.diy/icons/svelte.svg +1 -0
  413. tools/bolt.diy/icons/typescript.svg +1 -0
  414. tools/bolt.diy/icons/vite.svg +1 -0
  415. tools/bolt.diy/icons/vue.svg +1 -0
  416. tools/bolt.diy/load-context.ts +9 -0
  417. tools/bolt.diy/notarize.cjs +31 -0
  418. tools/bolt.diy/package.json +218 -0
  419. tools/bolt.diy/playwright.config.preview.ts +35 -0
  420. tools/bolt.diy/pre-start.cjs +26 -0
  421. tools/bolt.diy/public/apple-touch-icon-precomposed.png +0 -0
  422. tools/bolt.diy/public/apple-touch-icon.png +0 -0
  423. tools/bolt.diy/public/favicon.ico +0 -0
  424. tools/bolt.diy/public/favicon.svg +4 -0
  425. tools/bolt.diy/public/icons/AmazonBedrock.svg +1 -0
  426. tools/bolt.diy/public/icons/Anthropic.svg +4 -0
  427. tools/bolt.diy/public/icons/Cohere.svg +4 -0
  428. tools/bolt.diy/public/icons/Deepseek.svg +5 -0
  429. tools/bolt.diy/public/icons/Default.svg +4 -0
  430. tools/bolt.diy/public/icons/Google.svg +4 -0
  431. tools/bolt.diy/public/icons/Groq.svg +4 -0
  432. tools/bolt.diy/public/icons/HuggingFace.svg +4 -0
  433. tools/bolt.diy/public/icons/Hyperbolic.svg +3 -0
  434. tools/bolt.diy/public/icons/LMStudio.svg +5 -0
  435. tools/bolt.diy/public/icons/Mistral.svg +4 -0
  436. tools/bolt.diy/public/icons/Ollama.svg +4 -0
  437. tools/bolt.diy/public/icons/OpenAI.svg +4 -0
  438. tools/bolt.diy/public/icons/OpenAILike.svg +4 -0
  439. tools/bolt.diy/public/icons/OpenRouter.svg +4 -0
  440. tools/bolt.diy/public/icons/Perplexity.svg +4 -0
  441. tools/bolt.diy/public/icons/Together.svg +4 -0
  442. tools/bolt.diy/public/icons/xAI.svg +5 -0
  443. tools/bolt.diy/public/inspector-script.js +292 -0
  444. tools/bolt.diy/public/logo-dark-styled.png +0 -0
  445. tools/bolt.diy/public/logo-dark.png +0 -0
  446. tools/bolt.diy/public/logo-light-styled.png +0 -0
  447. tools/bolt.diy/public/logo-light.png +0 -0
  448. tools/bolt.diy/public/logo.svg +15 -0
  449. tools/bolt.diy/public/social_preview_index.jpg +0 -0
  450. tools/bolt.diy/scripts/clean.js +45 -0
  451. tools/bolt.diy/scripts/electron-dev.mjs +181 -0
  452. tools/bolt.diy/scripts/setup-env.sh +41 -0
  453. tools/bolt.diy/scripts/update-imports.sh +7 -0
  454. tools/bolt.diy/scripts/update.sh +52 -0
  455. tools/bolt.diy/services/execution-governor/Dockerfile +41 -0
  456. tools/bolt.diy/services/execution-governor/config.ts +42 -0
  457. tools/bolt.diy/services/execution-governor/index.ts +683 -0
  458. tools/bolt.diy/services/execution-governor/metrics.ts +141 -0
  459. tools/bolt.diy/services/execution-governor/package.json +31 -0
  460. tools/bolt.diy/services/execution-governor/priority-queue.ts +139 -0
  461. tools/bolt.diy/services/execution-governor/tsconfig.json +21 -0
  462. tools/bolt.diy/services/execution-governor/types.ts +145 -0
  463. tools/bolt.diy/services/headless-executor/Dockerfile +43 -0
  464. tools/bolt.diy/services/headless-executor/executor.ts +210 -0
  465. tools/bolt.diy/services/headless-executor/index.ts +323 -0
  466. tools/bolt.diy/services/headless-executor/package.json +27 -0
  467. tools/bolt.diy/services/headless-executor/tsconfig.json +21 -0
  468. tools/bolt.diy/services/headless-executor/types.ts +38 -0
  469. tools/bolt.diy/test-workflows.sh +240 -0
  470. tools/bolt.diy/tests/integration/corporate-swarm.test.ts +208 -0
  471. tools/bolt.diy/tests/mandates/budget-limited.json +34 -0
  472. tools/bolt.diy/tests/mandates/complex.json +53 -0
  473. tools/bolt.diy/tests/mandates/constraint-enforced.json +36 -0
  474. tools/bolt.diy/tests/mandates/simple.json +35 -0
  475. tools/bolt.diy/tsconfig.json +37 -0
  476. tools/bolt.diy/types/istextorbinary.d.ts +15 -0
  477. tools/bolt.diy/uno.config.ts +279 -0
  478. tools/bolt.diy/vite-electron.config.ts +76 -0
  479. tools/bolt.diy/vite.config.ts +112 -0
  480. tools/bolt.diy/worker-configuration.d.ts +22 -0
  481. tools/bolt.diy/wrangler.toml +6 -0
  482. tools/code_generator.py +461 -0
  483. tools/file_operations.py +465 -0
  484. tools/mandate_generator.py +337 -0
  485. tools/mcpClientUtils.py +1216 -0
  486. tools/sensors.py +285 -0
  487. utils/Agent_types.py +15 -0
  488. utils/AnyToStr.py +0 -0
  489. utils/HHCS.py +277 -0
  490. utils/__init__.py +30 -0
  491. utils/agent.py +3627 -0
  492. utils/aop.py +2948 -0
  493. utils/canonical.py +143 -0
  494. utils/conversation.py +1195 -0
  495. utils/doctrine_versioning +230 -0
  496. utils/formatter.py +474 -0
  497. utils/ledger.py +311 -0
  498. utils/out_types.py +16 -0
  499. utils/rollback.py +339 -0
  500. utils/router.py +929 -0
  501. utils/tui.py +1908 -0
@@ -0,0 +1,1168 @@
1
+ """Policy loop mixin for temporal policy engine.
2
+
3
+ This module provides PolicyLoopMixin for executing temporal policy loops with:
4
+ - Online learning (RLS + Bayesian regression)
5
+ - Multi-objective constrained optimization
6
+ - Drift detection (CUSUM)
7
+ - Deterministic decision-making
8
+
9
+ This is a feature mixin that can be composed into agent classes (R1 requirement).
10
+ """
11
+
12
+ from typing import Any, Callable, Dict, List, Optional, Tuple
13
+ from datetime import datetime, timezone
14
+ import numpy as np
15
+ from loguru import logger
16
+
17
+ from schemas.policy import (
18
+ DoctrineV1,
19
+ CompiledPolicy,
20
+ ObservationEvent,
21
+ DecisionEvent,
22
+ OutcomeEvent,
23
+ InterventionSpec,
24
+ Intervention,
25
+ ModelState,
26
+ LedgerEvent
27
+ )
28
+ from utils.canonical import stable_hash
29
+ from utils.ledger import Ledger
30
+
31
+ # Try to import scipy for numerical stability
32
+ try:
33
+ from scipy import linalg as scipy_linalg
34
+ SCIPY_AVAILABLE = True
35
+ except ImportError:
36
+ SCIPY_AVAILABLE = False
37
+ logger.warning("scipy not available - using numpy for matrix operations")
38
+
39
+ # Try to import sensors/actuators
40
+ try:
41
+ from tools.sensors import SensorRegistry
42
+ from tools.actuators import ActuatorRegistry
43
+ SENSORS_AVAILABLE = True
44
+ except ImportError:
45
+ SENSORS_AVAILABLE = False
46
+ SensorRegistry = None
47
+ ActuatorRegistry = None
48
+ logger.warning("tools.sensors/actuators not available - using callable functions only")
49
+
50
+ # Try to import MPC planner
51
+ try:
52
+ from templates.mpc_planner import MPCPlanner
53
+ MPC_AVAILABLE = True
54
+ except ImportError:
55
+ MPC_AVAILABLE = False
56
+ MPCPlanner = None
57
+ logger.warning("templates.mpc_planner not available - MPC planning disabled")
58
+
59
+ # Try to import drift detection
60
+ try:
61
+ from templates.drift_detection import DriftDetector, HybridDriftDetector
62
+ DRIFT_DETECTION_AVAILABLE = True
63
+ except ImportError:
64
+ DRIFT_DETECTION_AVAILABLE = False
65
+ DriftDetector = None
66
+ HybridDriftDetector = None
67
+ logger.warning("templates.drift_detection not available - using CUSUM only")
68
+
69
+ # Try to import rollback manager
70
+ try:
71
+ from utils.rollback import RollbackManager
72
+ ROLLBACK_AVAILABLE = True
73
+ except ImportError:
74
+ ROLLBACK_AVAILABLE = False
75
+ RollbackManager = None
76
+ logger.warning("utils.rollback not available - rollback functionality disabled")
77
+
78
+
79
+ class InterventionCatalog:
80
+ """Registry mapping lever IDs to intervention constructors.
81
+
82
+ Provides deterministic intervention creation with bounds enforcement.
83
+ """
84
+
85
+ def __init__(self, doctrine: DoctrineV1):
86
+ """
87
+ Initialize catalog from doctrine.
88
+
89
+ Args:
90
+ doctrine: Doctrine containing lever specifications
91
+ """
92
+ self.doctrine = doctrine
93
+ self.constructors: Dict[str, Callable] = {}
94
+ self._register_default_constructors()
95
+
96
+ def _register_default_constructors(self) -> None:
97
+ """Register default intervention constructors."""
98
+ for lever_id, lever_spec in self.doctrine.levers.items():
99
+ self.constructors[lever_id] = self._create_constructor(lever_id, lever_spec)
100
+
101
+ def _create_constructor(self, lever_id: str, lever_spec) -> Callable:
102
+ """Create a constructor function for a lever."""
103
+ def constructor(**params) -> InterventionSpec:
104
+ # Validate bounds
105
+ bounds = lever_spec.bounds
106
+ for param_name, param_value in params.items():
107
+ if param_name in bounds:
108
+ if "min" in bounds[param_name] and param_value < bounds[param_name]["min"]:
109
+ raise ValueError(f"Parameter {param_name} below minimum: {param_value} < {bounds[param_name]['min']}")
110
+ if "max" in bounds[param_name] and param_value > bounds[param_name]["max"]:
111
+ raise ValueError(f"Parameter {param_name} above maximum: {param_value} > {bounds[param_name]['max']}")
112
+
113
+ return InterventionSpec(
114
+ lever_id=lever_id,
115
+ parameters=params,
116
+ rollback_descriptor={"lever_id": lever_id, "parameters": params} if lever_spec.rollback_required else None
117
+ )
118
+
119
+ return constructor
120
+
121
+ def create_intervention(self, lever_id: str, **params) -> InterventionSpec:
122
+ """
123
+ Create an intervention from a lever ID and parameters.
124
+
125
+ Args:
126
+ lever_id: ID of the lever
127
+ **params: Intervention parameters
128
+
129
+ Returns:
130
+ InterventionSpec: Intervention specification
131
+ """
132
+ if lever_id not in self.constructors:
133
+ raise ValueError(f"Unknown lever ID: {lever_id}")
134
+ return self.constructors[lever_id](**params)
135
+
136
+
137
+ class PolicyLoopMixin:
138
+ """Mixin class for temporal policy loop execution.
139
+
140
+ Provides methods for:
141
+ - run_epoch(): Execute single epoch (observe → plan → act → update)
142
+ - plan_actions(): Multi-objective constrained optimization
143
+ - update_models(): Online learning (RLS + Bayesian regression)
144
+ - explain_decision(): Generate rationale with decision hash
145
+ """
146
+
147
+ def __init__(
148
+ self,
149
+ doctrine: DoctrineV1,
150
+ ledger: Ledger,
151
+ seed: int = 42,
152
+ sensor_registry: Optional[Any] = None,
153
+ actuator_registry: Optional[Any] = None
154
+ ):
155
+ """
156
+ Initialize policy loop mixin.
157
+
158
+ Args:
159
+ doctrine: Policy doctrine
160
+ ledger: Event ledger
161
+ seed: Random seed for determinism
162
+ sensor_registry: Optional SensorRegistry instance
163
+ actuator_registry: Optional ActuatorRegistry instance
164
+ """
165
+ self.doctrine = doctrine
166
+ self.compiled_policy = CompiledPolicy.compile(doctrine)
167
+ self.ledger = ledger
168
+ self.seed = seed
169
+ self.rng = np.random.default_rng(seed)
170
+
171
+ # Sensor and actuator registries
172
+ self.sensor_registry = sensor_registry
173
+ self.actuator_registry = actuator_registry
174
+
175
+ # Auto-wire sensors/actuators from doctrine if registries provided
176
+ if self.sensor_registry and SENSORS_AVAILABLE:
177
+ # Auto-discover sensors
178
+ self.sensor_registry.auto_discover()
179
+
180
+ # MPC planner (optional)
181
+ self.use_mpc = False # Can be enabled via set_mpc_mode()
182
+ self.mpc_planner: Optional[Any] = None
183
+ if MPC_AVAILABLE and MPCPlanner:
184
+ try:
185
+ self.mpc_planner = MPCPlanner(doctrine, horizon=5)
186
+ except Exception as e:
187
+ logger.warning(f"Failed to initialize MPC planner: {e}")
188
+
189
+ # Intervention catalog
190
+ self.intervention_catalog = InterventionCatalog(doctrine)
191
+
192
+ # Model state (learned parameters θ)
193
+ self.model_state: Optional[ModelState] = None
194
+
195
+ # Feature history for lag/EMA features
196
+ self.feature_history: List[Dict[str, float]] = []
197
+ self.max_history = 10 # Keep last 10 epochs
198
+
199
+ # EMA parameters
200
+ self.ema_alpha = 0.3 # EMA smoothing factor
201
+ self.ema_features: Dict[str, float] = {}
202
+
203
+ # Drift detection (CUSUM + optional ruptures)
204
+ self.drift_detection_mode = "cusum" # "cusum", "ruptures", "hybrid"
205
+ self.cusum_stat = 0.0
206
+ self.cusum_k = 0.5 # CUSUM threshold parameter
207
+ self.cusum_h = 5.0 # CUSUM alarm threshold
208
+ self.conservative_mode = False
209
+
210
+ # Ruptures-based drift detection (optional)
211
+ self.drift_detector: Optional[Any] = None
212
+ self.metric_history: Dict[str, List[float]] = {} # Per-metric history for ruptures
213
+ if DRIFT_DETECTION_AVAILABLE and HybridDriftDetector:
214
+ try:
215
+ self.drift_detector = HybridDriftDetector(use_ruptures=True)
216
+ except Exception as e:
217
+ logger.warning(f"Failed to initialize drift detector: {e}")
218
+
219
+ # Rollback manager (optional)
220
+ self.rollback_manager: Optional[Any] = None
221
+ self.auto_rollback_on_error = False # Auto-rollback on actuator errors
222
+ self.auto_rollback_on_invariant = False # Auto-rollback on invariant violations
223
+
224
+ # RLS parameters for transition model
225
+ self.rls_lambda = 0.95 # Forgetting factor
226
+ self.rls_A: Optional[np.ndarray] = None # State transition matrix
227
+ self.rls_B: Optional[np.ndarray] = None # Action effect matrix
228
+ self.rls_P: Dict[int, np.ndarray] = {} # Parameter covariance (per output)
229
+ self.rls_theta: Dict[int, np.ndarray] = {} # Parameter vector (per output)
230
+
231
+ # Bayesian regression for causal effects
232
+ self.bayesian_tau = 1.0 # Prior variance
233
+ self.bayesian_sigma = 0.1 # Observation noise
234
+ self.bayesian_beta_mu: Optional[np.ndarray] = None # Posterior mean
235
+ self.bayesian_beta_sigma: Optional[np.ndarray] = None # Posterior covariance
236
+
237
+ # Action history for causal effect estimation
238
+ self.action_history: List[Dict[str, Any]] = []
239
+ self.metric_delta_history: List[Dict[str, float]] = []
240
+
241
+ def extract_features(
242
+ self,
243
+ snapshot: Dict[str, float],
244
+ k: int = 3
245
+ ) -> np.ndarray:
246
+ """
247
+ Extract feature vector x_t = φ(s_t, L_{t-k:t}).
248
+
249
+ Includes:
250
+ - Instantaneous metrics
251
+ - Lag features (x_{t-1}, x_{t-2}, ...)
252
+ - EMA features
253
+ - Volatility (windowed std)
254
+ - Drift features (CUSUM/z-score)
255
+
256
+ Args:
257
+ snapshot: Current state snapshot (metric_name -> value)
258
+ k: Number of lag features to include
259
+
260
+ Returns:
261
+ np.ndarray: Feature vector
262
+ """
263
+ # Get metric values in deterministic order
264
+ metric_names = sorted(self.doctrine.metrics.keys())
265
+ n_metrics = len(metric_names)
266
+
267
+ # Instantaneous features
268
+ instant_features = np.array([snapshot.get(name, 0.0) for name in metric_names])
269
+
270
+ # Lag features
271
+ lag_features = []
272
+ for lag in range(1, min(k + 1, len(self.feature_history) + 1)):
273
+ if len(self.feature_history) >= lag:
274
+ lag_snapshot = self.feature_history[-lag]
275
+ lag_vals = np.array([lag_snapshot.get(name, 0.0) for name in metric_names])
276
+ else:
277
+ lag_vals = np.zeros(n_metrics)
278
+ lag_features.append(lag_vals)
279
+
280
+ # Pad if needed
281
+ while len(lag_features) < k:
282
+ lag_features.insert(0, np.zeros(n_metrics))
283
+
284
+ lag_features = np.concatenate(lag_features) if lag_features else np.array([])
285
+
286
+ # EMA features
287
+ ema_features = []
288
+ for name in metric_names:
289
+ current_val = snapshot.get(name, 0.0)
290
+ if name in self.ema_features:
291
+ ema_val = self.ema_alpha * current_val + (1 - self.ema_alpha) * self.ema_features[name]
292
+ else:
293
+ ema_val = current_val
294
+ self.ema_features[name] = ema_val
295
+ ema_features.append(ema_val)
296
+ ema_features = np.array(ema_features)
297
+
298
+ # Volatility (windowed std)
299
+ if len(self.feature_history) >= 3:
300
+ recent_values = [
301
+ [hist.get(name, 0.0) for name in metric_names]
302
+ for hist in self.feature_history[-3:]
303
+ ]
304
+ volatility = np.std(recent_values, axis=0)
305
+ else:
306
+ volatility = np.zeros(n_metrics)
307
+
308
+ # Drift features (z-score of current vs EMA)
309
+ drift_features = []
310
+ for i, name in enumerate(metric_names):
311
+ current_val = snapshot.get(name, 0.0)
312
+ ema_val = self.ema_features.get(name, current_val)
313
+ if volatility[i] > 1e-6:
314
+ z_score = (current_val - ema_val) / volatility[i]
315
+ else:
316
+ z_score = 0.0
317
+ drift_features.append(z_score)
318
+ drift_features = np.array(drift_features)
319
+
320
+ # Concatenate all features
321
+ features = np.concatenate([
322
+ instant_features,
323
+ lag_features,
324
+ ema_features,
325
+ volatility,
326
+ drift_features
327
+ ])
328
+
329
+ return features
330
+
331
+ def update_transition_model(
332
+ self,
333
+ x_t: np.ndarray,
334
+ u_t: np.ndarray,
335
+ x_tp1: np.ndarray
336
+ ) -> None:
337
+ """
338
+ Update transition model using Recursive Least Squares (RLS).
339
+
340
+ Model: x_{t+1} = Ax_t + Bu_t + ε, ε ~ N(0, Σ)
341
+
342
+ Args:
343
+ x_t: State at time t
344
+ u_t: Action vector at time t
345
+ x_tp1: State at time t+1
346
+ """
347
+ # Construct regressor: Φ_t = [x_t; u_t]
348
+ Phi_t = np.concatenate([x_t, u_t])
349
+ n_inputs = len(Phi_t)
350
+ n_outputs = len(x_tp1)
351
+
352
+ # Initialize if needed - use per-output RLS
353
+ if not self.rls_theta:
354
+ # Will be initialized per output dimension below
355
+ pass
356
+
357
+ # For each output dimension, maintain separate RLS
358
+ # We only learn to predict the instantaneous metrics (first n_metrics elements)
359
+ n_metrics = len(self.doctrine.metrics)
360
+ n_outputs_to_learn = min(n_outputs, n_metrics)
361
+
362
+ for i in range(n_outputs_to_learn):
363
+ # Check if we need to reinitialize due to size change
364
+ if i not in self.rls_theta or len(self.rls_theta[i]) != n_inputs:
365
+ # Initialize or reinitialize for this output dimension
366
+ self.rls_theta[i] = np.zeros(n_inputs)
367
+ self.rls_P[i] = np.eye(n_inputs) * 100.0 # Large initial covariance
368
+
369
+ # Get regressor for this output
370
+ phi = Phi_t
371
+
372
+ # Kalman gain for this output
373
+ P_phi = self.rls_P[i] @ phi
374
+ denom = self.rls_lambda + np.dot(phi, P_phi)
375
+ if denom < 1e-10:
376
+ continue
377
+
378
+ K = P_phi / denom
379
+
380
+ # Prediction
381
+ pred = np.dot(self.rls_theta[i], phi)
382
+ # Target is the instantaneous metric value (first n_metrics elements of x_tp1)
383
+ target_idx = i if i < len(x_tp1) else 0
384
+ error = x_tp1[target_idx] - pred
385
+
386
+ # Update parameter
387
+ self.rls_theta[i] = self.rls_theta[i] + K * error
388
+
389
+ # Update covariance
390
+ self.rls_P[i] = (self.rls_P[i] - np.outer(K, P_phi)) / self.rls_lambda
391
+
392
+ # Initialize A and B matrices if needed (for prediction)
393
+ if self.rls_A is None:
394
+ self.rls_A = np.eye(n_outputs) * 0.9 # Slight decay
395
+ self.rls_B = np.zeros((n_outputs, max(1, n_inputs - n_outputs))) # Action effects
396
+
397
+ # Store updated model state
398
+ theta_dict = {str(k): v.tolist() for k, v in self.rls_theta.items()} if isinstance(self.rls_theta, dict) else {}
399
+ P_dict = {str(k): v.tolist() for k, v in self.rls_P.items()} if isinstance(self.rls_P, dict) else {}
400
+
401
+ self.model_state = ModelState.create(
402
+ parameters={
403
+ "A": self.rls_A.tolist() if self.rls_A is not None else [],
404
+ "B": self.rls_B.tolist() if self.rls_B is not None else [],
405
+ "theta": theta_dict
406
+ },
407
+ covariance={
408
+ "P": P_dict
409
+ }
410
+ )
411
+
412
+ def predict_next_state(
413
+ self,
414
+ x_t: np.ndarray,
415
+ u_t: np.ndarray
416
+ ) -> Tuple[np.ndarray, np.ndarray]:
417
+ """
418
+ Predict next state and uncertainty.
419
+
420
+ Args:
421
+ x_t: Current state (feature vector)
422
+ u_t: Action vector
423
+
424
+ Returns:
425
+ Tuple[np.ndarray, np.ndarray]: (predicted_state, uncertainty)
426
+ """
427
+ Phi_t = np.concatenate([x_t, u_t])
428
+ n_inputs = len(Phi_t)
429
+
430
+ # Get number of metrics (output dimensions)
431
+ n_metrics = len(self.doctrine.metrics)
432
+
433
+ if not self.rls_theta:
434
+ # No model yet - return current state with high uncertainty
435
+ # Extract just the instantaneous metrics from feature vector
436
+ instant_features = x_t[:n_metrics] if len(x_t) >= n_metrics else x_t
437
+ return instant_features.copy(), np.ones(len(instant_features)) * 10.0
438
+
439
+ # Predict using learned parameters
440
+ # We predict the instantaneous metrics (first n_metrics elements of feature vector)
441
+ x_pred = np.zeros(n_metrics)
442
+ uncertainty = np.zeros(n_metrics)
443
+
444
+ for i in range(n_metrics):
445
+ if i in self.rls_theta:
446
+ # Check if parameter vector size matches
447
+ if len(self.rls_theta[i]) == n_inputs:
448
+ # Use learned parameters
449
+ x_pred[i] = np.dot(self.rls_theta[i], Phi_t)
450
+ # Uncertainty from covariance diagonal
451
+ if i in self.rls_P and self.rls_P[i].size > 0:
452
+ uncertainty[i] = np.sqrt(np.abs(self.rls_P[i][0, 0]))
453
+ else:
454
+ uncertainty[i] = 1.0
455
+ else:
456
+ # Size mismatch - reinitialize for this output
457
+ self.rls_theta[i] = np.zeros(n_inputs)
458
+ self.rls_P[i] = np.eye(n_inputs) * 100.0
459
+ x_pred[i] = x_t[i] if i < len(x_t) else 0.0
460
+ uncertainty[i] = 10.0
461
+ else:
462
+ # Not initialized yet
463
+ x_pred[i] = x_t[i] if i < len(x_t) else 0.0
464
+ uncertainty[i] = 10.0
465
+
466
+ return x_pred, uncertainty
467
+
468
+ def update_causal_effects(self, epoch: int) -> None:
469
+ """
470
+ Update causal effect estimates using Bayesian linear regression.
471
+
472
+ Model: Δm_t = β^T z_t + η_t
473
+
474
+ Args:
475
+ epoch: Current epoch
476
+ """
477
+ # Get deltas from ledger
478
+ deltas = self.ledger.compute_deltas(epoch)
479
+ if not deltas:
480
+ return
481
+
482
+ # Get recent actions and outcomes
483
+ if len(self.action_history) == 0 or len(self.metric_delta_history) == 0:
484
+ return
485
+
486
+ # For each metric, estimate causal effect
487
+ metric_names = sorted(self.doctrine.metrics.keys())
488
+
489
+ for metric_name in metric_names:
490
+ if metric_name not in deltas:
491
+ continue
492
+
493
+ # Collect data: (action_features, delta)
494
+ Z = [] # Action features
495
+ y = [] # Metric deltas
496
+
497
+ for i, (action, delta_dict) in enumerate(zip(self.action_history, self.metric_delta_history)):
498
+ if metric_name in delta_dict:
499
+ # Encode action as feature vector
500
+ action_vec = self._encode_action(action)
501
+ Z.append(action_vec)
502
+ y.append(delta_dict[metric_name])
503
+
504
+ if len(Z) < 2:
505
+ continue
506
+
507
+ Z = np.array(Z)
508
+ y = np.array(y)
509
+
510
+ # Bayesian regression: β ~ N(0, τ²I), η ~ N(0, σ²)
511
+ # Posterior: Σ_β = (σ⁻² Z^T Z + τ⁻² I)⁻¹, μ_β = σ⁻² Σ_β Z^T y
512
+
513
+ n_features = Z.shape[1]
514
+ I = np.eye(n_features)
515
+
516
+ # Compute posterior
517
+ ZTZ = Z.T @ Z
518
+ sigma_inv_sq = 1.0 / (self.bayesian_sigma ** 2)
519
+ tau_inv_sq = 1.0 / (self.bayesian_tau ** 2)
520
+
521
+ try:
522
+ if SCIPY_AVAILABLE:
523
+ Sigma_beta_inv = sigma_inv_sq * ZTZ + tau_inv_sq * I
524
+ Sigma_beta = scipy_linalg.inv(Sigma_beta_inv)
525
+ else:
526
+ Sigma_beta_inv = sigma_inv_sq * ZTZ + tau_inv_sq * I
527
+ Sigma_beta = np.linalg.inv(Sigma_beta_inv)
528
+
529
+ mu_beta = sigma_inv_sq * Sigma_beta @ Z.T @ y
530
+
531
+ # Store for this metric
532
+ if self.bayesian_beta_mu is None:
533
+ self.bayesian_beta_mu = {}
534
+ if self.bayesian_beta_sigma is None:
535
+ self.bayesian_beta_sigma = {}
536
+
537
+ self.bayesian_beta_mu[metric_name] = mu_beta
538
+ self.bayesian_beta_sigma[metric_name] = Sigma_beta
539
+
540
+ except np.linalg.LinAlgError:
541
+ logger.warning(f"Singular matrix in Bayesian regression for {metric_name}")
542
+ continue
543
+
544
+ def _encode_action(self, action: Dict[str, Any]) -> np.ndarray:
545
+ """Encode action as feature vector for causal effect estimation."""
546
+ # Simple encoding: one-hot for lever_id + parameter values
547
+ lever_ids = sorted(self.doctrine.levers.keys())
548
+ n_levers = len(lever_ids)
549
+
550
+ # One-hot lever ID
551
+ lever_vec = np.zeros(n_levers)
552
+ if "lever_id" in action:
553
+ try:
554
+ idx = lever_ids.index(action["lever_id"])
555
+ lever_vec[idx] = 1.0
556
+ except ValueError:
557
+ pass
558
+
559
+ # Parameter values (normalized)
560
+ param_vec = []
561
+ if "parameters" in action:
562
+ for lever_id in lever_ids:
563
+ if lever_id in self.doctrine.levers:
564
+ lever_spec = self.doctrine.levers[lever_id]
565
+ for param_name, param_value in action["parameters"].items():
566
+ if param_name in lever_spec.bounds:
567
+ bounds = lever_spec.bounds[param_name]
568
+ if "min" in bounds and "max" in bounds:
569
+ # Normalize to [0, 1]
570
+ norm_val = (param_value - bounds["min"]) / (bounds["max"] - bounds["min"])
571
+ param_vec.append(norm_val)
572
+ else:
573
+ param_vec.append(param_value)
574
+ else:
575
+ param_vec.append(param_value)
576
+
577
+ return np.concatenate([lever_vec, np.array(param_vec)])
578
+
579
+ def set_mpc_mode(self, use_mpc: bool = True, horizon: int = 5, use_robust: bool = False) -> None:
580
+ """
581
+ Enable or disable MPC planning mode.
582
+
583
+ Args:
584
+ use_mpc: Whether to use MPC planner
585
+ horizon: Prediction horizon for MPC
586
+ use_robust: Whether to use robust MPC
587
+ """
588
+ self.use_mpc = use_mpc
589
+ if use_mpc and MPC_AVAILABLE and MPCPlanner:
590
+ try:
591
+ self.mpc_planner = MPCPlanner(self.doctrine, horizon=horizon, use_robust=use_robust)
592
+ except Exception as e:
593
+ logger.warning(f"Failed to initialize MPC planner: {e}")
594
+ self.use_mpc = False
595
+
596
+ def plan_actions(
597
+ self,
598
+ x_t: np.ndarray,
599
+ current_metrics: Dict[str, float],
600
+ beam_width: int = 5
601
+ ) -> Tuple[List[InterventionSpec], float, str]:
602
+ """
603
+ Plan actions using constrained multi-objective optimization.
604
+
605
+ Uses MPC if enabled, otherwise falls back to beam search.
606
+
607
+ Score: Σ w_i ΔĴ_i(a) - λ_c C(a) - λ_r R(a)
608
+
609
+ Args:
610
+ x_t: Current feature vector
611
+ current_metrics: Current metric values
612
+ beam_width: Beam search width (used if MPC not available)
613
+
614
+ Returns:
615
+ Tuple[List[InterventionSpec], float, str]: (chosen_interventions, score, rationale)
616
+ """
617
+ # Try MPC first if enabled
618
+ if self.use_mpc and self.mpc_planner:
619
+ try:
620
+ # Prepare objectives and constraints
621
+ objectives = [{"metric_name": obj.metric_name, "direction": obj.direction, "weight": self.compiled_policy.normalized_weights.get(obj.metric_name, 1.0)} for obj in self.doctrine.objectives]
622
+ constraints = [{"condition": inv.condition} for inv in self.doctrine.invariants]
623
+ lever_bounds = {lever_id: lever_spec.bounds for lever_id, lever_spec in self.doctrine.levers.items()}
624
+
625
+ # Get transition model matrices
626
+ A = self.rls_A
627
+ B = self.rls_B
628
+
629
+ interventions, score, rationale = self.mpc_planner.solve_mpc(
630
+ x_t, A, B, objectives, constraints, lever_bounds
631
+ )
632
+
633
+ if interventions: # MPC succeeded
634
+ return interventions, score, f"MPC: {rationale}"
635
+ else:
636
+ # Fall through to beam search
637
+ logger.debug(f"MPC returned no interventions, falling back to beam search: {rationale}")
638
+ except Exception as e:
639
+ logger.warning(f"MPC planning failed, falling back to beam search: {e}")
640
+
641
+ # Fallback to beam search
642
+ # Generate candidate actions from allowed levers
643
+ candidates = []
644
+
645
+ for lever_id, lever_spec in self.doctrine.levers.items():
646
+ # Generate parameter combinations (simplified - grid search)
647
+ bounds = lever_spec.bounds
648
+
649
+ # Simple strategy: try a few parameter values
650
+ param_combos = [{}] # No-op action
651
+
652
+ # Add some parameter variations
653
+ for param_name, param_bounds in bounds.items():
654
+ if "min" in param_bounds and "max" in param_bounds:
655
+ min_val = param_bounds["min"]
656
+ max_val = param_bounds["max"]
657
+ # Try low, medium, high
658
+ for val in [min_val, (min_val + max_val) / 2, max_val]:
659
+ new_combos = []
660
+ for combo in param_combos:
661
+ new_combo = combo.copy()
662
+ new_combo[param_name] = val
663
+ new_combos.append(new_combo)
664
+ param_combos.extend(new_combos)
665
+
666
+ # Limit combinations
667
+ param_combos = param_combos[:10]
668
+
669
+ for params in param_combos:
670
+ try:
671
+ intervention = self.intervention_catalog.create_intervention(lever_id, **params)
672
+ candidates.append(intervention)
673
+ except Exception as e:
674
+ logger.debug(f"Invalid intervention: {e}")
675
+ continue
676
+
677
+ # Score each candidate
678
+ scored_candidates = []
679
+
680
+ for candidate in candidates:
681
+ # Encode action
682
+ u_t = self._encode_action({"lever_id": candidate.lever_id, "parameters": candidate.parameters})
683
+
684
+ # Predict effect
685
+ x_pred, uncertainty = self.predict_next_state(x_t, u_t)
686
+
687
+ # Compute objective improvement
688
+ score = 0.0
689
+ rationale_parts = []
690
+
691
+ for obj in self.doctrine.objectives:
692
+ metric_name = obj.metric_name
693
+ weight = self.compiled_policy.normalized_weights.get(metric_name, 1.0)
694
+
695
+ # Get predicted metric change (simplified - use first metric dimension)
696
+ metric_idx = sorted(self.doctrine.metrics.keys()).index(metric_name) if metric_name in self.doctrine.metrics else 0
697
+ if metric_idx < len(x_pred):
698
+ delta_pred = x_pred[metric_idx] - x_t[metric_idx] if metric_idx < len(x_t) else 0.0
699
+ else:
700
+ delta_pred = 0.0
701
+
702
+ # Use causal effect estimate if available
703
+ if metric_name in (self.bayesian_beta_mu or {}):
704
+ beta_mu = self.bayesian_beta_mu[metric_name]
705
+ causal_effect = np.dot(beta_mu, u_t)
706
+ delta_pred = causal_effect
707
+
708
+ # Objective contribution
709
+ if obj.direction == "minimize":
710
+ improvement = -delta_pred * weight
711
+ else:
712
+ improvement = delta_pred * weight
713
+
714
+ score += improvement
715
+ rationale_parts.append(f"{metric_name}: {improvement:.3f}")
716
+
717
+ # Cost penalty
718
+ cost = candidate.cost or 0.0
719
+ score -= 0.1 * cost # Cost penalty weight
720
+
721
+ # Risk penalty (from uncertainty)
722
+ risk = np.mean(uncertainty)
723
+ score -= 0.2 * risk # Risk penalty weight
724
+
725
+ # Check invariants
726
+ violates_invariant = False
727
+ for inv in self.doctrine.invariants:
728
+ # Simple invariant check (full version would evaluate condition)
729
+ if "never_touch" in inv.condition.lower():
730
+ # Check if lever targets forbidden items
731
+ if any(forbidden in str(candidate.parameters) for forbidden in ["sshd", "NetworkManager"]):
732
+ violates_invariant = True
733
+ break
734
+
735
+ if violates_invariant:
736
+ score = -1e6 # Heavy penalty
737
+
738
+ scored_candidates.append((candidate, score, " | ".join(rationale_parts)))
739
+
740
+ # Beam search: keep top beam_width candidates
741
+ scored_candidates.sort(key=lambda x: x[1], reverse=True)
742
+ top_candidates = scored_candidates[:beam_width]
743
+
744
+ # Apply risk budget
745
+ total_risk = 0.0
746
+ selected = []
747
+
748
+ for candidate, score, rationale in top_candidates:
749
+ if len(selected) >= self.doctrine.risk_budget.max_actions_per_epoch:
750
+ break
751
+
752
+ # Estimate risk
753
+ u_t = self._encode_action({"lever_id": candidate.lever_id, "parameters": candidate.parameters})
754
+ _, uncertainty = self.predict_next_state(x_t, u_t)
755
+ risk = np.mean(uncertainty)
756
+
757
+ if total_risk + risk > self.doctrine.risk_budget.max_risk_per_epoch:
758
+ if self.doctrine.risk_budget.rollback_required:
759
+ break # Stop if rollback required
760
+ else:
761
+ continue # Skip this action
762
+
763
+ selected.append(candidate)
764
+ total_risk += risk
765
+
766
+ # Return best action (or empty if conservative mode)
767
+ if self.conservative_mode and len(selected) > 1:
768
+ selected = selected[:1] # Only one action in conservative mode
769
+
770
+ if not selected:
771
+ return [], 0.0, "No actions selected (risk budget or conservative mode)"
772
+
773
+ best_score = top_candidates[0][1] if top_candidates else 0.0
774
+ best_rationale = top_candidates[0][2] if top_candidates else "No rationale"
775
+
776
+ return selected, best_score, best_rationale
777
+
778
+ def set_drift_detection_mode(self, mode: str = "cusum") -> None:
779
+ """
780
+ Set drift detection mode.
781
+
782
+ Args:
783
+ mode: Detection mode ("cusum", "ruptures", "hybrid")
784
+ """
785
+ self.drift_detection_mode = mode
786
+ if mode in ["ruptures", "hybrid"] and DRIFT_DETECTION_AVAILABLE and HybridDriftDetector:
787
+ try:
788
+ self.drift_detector = HybridDriftDetector(use_ruptures=(mode == "ruptures" or mode == "hybrid"))
789
+ except Exception as e:
790
+ logger.warning(f"Failed to initialize drift detector: {e}")
791
+ self.drift_detection_mode = "cusum"
792
+
793
+ def update_drift_detection(
794
+ self,
795
+ x_t: np.ndarray,
796
+ x_pred: np.ndarray
797
+ ) -> None:
798
+ """
799
+ Update drift detection using CUSUM or ruptures.
800
+
801
+ Args:
802
+ x_t: Actual state
803
+ x_pred: Predicted state
804
+ """
805
+ n_metrics = len(self.doctrine.metrics)
806
+ metric_names = sorted(self.doctrine.metrics.keys())
807
+
808
+ # Update per-metric history for ruptures
809
+ for i, metric_name in enumerate(metric_names):
810
+ if i < len(x_t):
811
+ if metric_name not in self.metric_history:
812
+ self.metric_history[metric_name] = []
813
+ self.metric_history[metric_name].append(float(x_t[i]))
814
+ # Keep only last 100 values
815
+ if len(self.metric_history[metric_name]) > 100:
816
+ self.metric_history[metric_name].pop(0)
817
+
818
+ # Use hybrid/ruptures if enabled
819
+ if self.drift_detection_mode in ["ruptures", "hybrid"] and self.drift_detector:
820
+ drift_detected = False
821
+ for i, metric_name in enumerate(metric_names):
822
+ if i < len(x_t) and i < len(x_pred):
823
+ detected, drift_info = self.drift_detector.update(
824
+ metric_name,
825
+ float(x_t[i]),
826
+ float(x_pred[i]),
827
+ self.metric_history.get(metric_name, [])
828
+ )
829
+ if detected:
830
+ drift_detected = True
831
+ logger.warning(f"Drift detected in {metric_name}: {drift_info}")
832
+
833
+ # Update conservative mode
834
+ if drift_detected:
835
+ self.conservative_mode = True
836
+ elif self.drift_detector.cusum_stat < self.cusum_h * 0.5:
837
+ self.conservative_mode = False
838
+
839
+ # Update CUSUM stat from detector
840
+ self.cusum_stat = self.drift_detector.cusum_stat
841
+ else:
842
+ # Fallback to CUSUM
843
+ residual = x_t - x_pred
844
+ r_t = np.mean(np.abs(residual)) # Mean absolute residual
845
+
846
+ # Update CUSUM
847
+ self.cusum_stat = max(0.0, self.cusum_stat + r_t - self.cusum_k)
848
+
849
+ # Check alarm
850
+ if self.cusum_stat > self.cusum_h:
851
+ if not self.conservative_mode:
852
+ logger.warning(f"Drift detected! CUSUM={self.cusum_stat:.3f} > {self.cusum_h}")
853
+ self.conservative_mode = True
854
+ elif self.cusum_stat < self.cusum_h * 0.5:
855
+ # Reset if well below threshold
856
+ self.conservative_mode = False
857
+
858
+ def explain_decision(
859
+ self,
860
+ interventions: List[InterventionSpec],
861
+ score: float,
862
+ rationale: str,
863
+ epoch: int,
864
+ x_t: np.ndarray
865
+ ) -> Tuple[str, str]:
866
+ """
867
+ Generate decision explanation and compute decision hash.
868
+
869
+ Args:
870
+ interventions: Chosen interventions
871
+ score: Optimization score
872
+ rationale: Rationale text
873
+ epoch: Current epoch
874
+ x_t: Current feature vector
875
+
876
+ Returns:
877
+ Tuple[str, str]: (explanation, decision_hash)
878
+ """
879
+ # Build explanation
880
+ explanation_parts = [
881
+ f"Epoch {epoch}",
882
+ f"Score: {score:.3f}",
883
+ f"Rationale: {rationale}",
884
+ f"Interventions: {len(interventions)}"
885
+ ]
886
+
887
+ for i, interv in enumerate(interventions):
888
+ explanation_parts.append(f" {i+1}. {interv.lever_id}: {interv.parameters}")
889
+
890
+ explanation = "\n".join(explanation_parts)
891
+
892
+ # Compute decision hash
893
+ # Extract just the instantaneous metrics for state hash (deterministic)
894
+ n_metrics = len(self.doctrine.metrics)
895
+ x_t_metrics = x_t[:n_metrics] if len(x_t) >= n_metrics else x_t
896
+
897
+ decision_data = {
898
+ "epoch": epoch,
899
+ "policy_hash": self.compiled_policy.policy_hash,
900
+ "state_hash": stable_hash(x_t_metrics.tolist()),
901
+ "interventions": [interv.model_dump() for interv in interventions],
902
+ "score": score,
903
+ "seed": self.seed
904
+ }
905
+
906
+ # Get ledger prefix hash (only observation/decision events, excluding timestamps)
907
+ ledger_events = self.ledger.window(epoch, k=epoch + 1)
908
+ # Hash only the payload content, not timestamps
909
+ ledger_hashes = []
910
+ for e in ledger_events:
911
+ if e["type"] in ["observation", "decision"]:
912
+ # Create deterministic hash from payload (excluding timestamp)
913
+ payload = e["payload"].copy()
914
+ if "timestamp" in payload:
915
+ del payload["timestamp"]
916
+ ledger_hashes.append(stable_hash(payload))
917
+ ledger_hash = stable_hash(sorted(ledger_hashes)) # Sort for determinism
918
+ decision_data["ledger_prefix_hash"] = ledger_hash
919
+
920
+ decision_hash = stable_hash(decision_data)
921
+
922
+ return explanation, decision_hash
923
+
924
+ def run_epoch(
925
+ self,
926
+ epoch: int,
927
+ sensor_provider: Optional[Callable] = None,
928
+ actuator: Optional[Callable] = None
929
+ ) -> Dict[str, Any]:
930
+ """
931
+ Execute single epoch: observe → plan → act → update.
932
+
933
+ Args:
934
+ epoch: Epoch number
935
+ sensor_provider: Function to get current state snapshot (returns Dict[str, float])
936
+ actuator: Function to execute interventions (takes List[InterventionSpec])
937
+
938
+ Returns:
939
+ Dict[str, Any]: Epoch results
940
+ """
941
+ # 1. Observe
942
+ if sensor_provider:
943
+ snapshot = sensor_provider()
944
+ elif self.sensor_registry:
945
+ # Use sensor registry if available
946
+ required_metrics = list(self.doctrine.metrics.keys())
947
+ snapshot = self.sensor_registry.read_all(required_metrics)
948
+ # Fill missing metrics with 0.0
949
+ for metric_name in required_metrics:
950
+ if metric_name not in snapshot:
951
+ snapshot[metric_name] = 0.0
952
+ else:
953
+ # Dummy sensor for base implementation
954
+ snapshot = {name: 0.0 for name in self.doctrine.metrics.keys()}
955
+
956
+ # Extract features
957
+ x_t = self.extract_features(snapshot)
958
+
959
+ # Record observation
960
+ obs_event = ObservationEvent(
961
+ epoch=epoch,
962
+ metrics=snapshot,
963
+ timestamp=datetime.now(timezone.utc).isoformat()
964
+ )
965
+ ledger_event = LedgerEvent.from_observation(obs_event)
966
+ self.ledger.append(ledger_event)
967
+
968
+ # Update feature history
969
+ self.feature_history.append(snapshot.copy())
970
+ if len(self.feature_history) > self.max_history:
971
+ self.feature_history.pop(0)
972
+
973
+ # 2. Plan
974
+ interventions, score, rationale = self.plan_actions(x_t, snapshot)
975
+
976
+ # Generate explanation and decision hash
977
+ explanation, decision_hash = self.explain_decision(interventions, score, rationale, epoch, x_t)
978
+
979
+ # Record decision
980
+ decision_event = DecisionEvent(
981
+ epoch=epoch,
982
+ interventions=[interv.model_dump() for interv in interventions],
983
+ rationale=rationale,
984
+ decision_hash=decision_hash,
985
+ score=score
986
+ )
987
+ ledger_event = LedgerEvent.from_decision(decision_event)
988
+ self.ledger.append(ledger_event)
989
+
990
+ # 3. Act
991
+ checkpoint_id = None
992
+ intervention_ids = []
993
+
994
+ if interventions:
995
+ # Create checkpoint before interventions if rollback enabled
996
+ if self.rollback_manager:
997
+ checkpoint_id = self.rollback_manager.create_checkpoint(epoch, snapshot)
998
+
999
+ if actuator:
1000
+ # Use provided actuator function
1001
+ try:
1002
+ actuator(interventions)
1003
+ # Record interventions
1004
+ if self.rollback_manager:
1005
+ for interv in interventions:
1006
+ interv_id = self.rollback_manager.record_intervention(
1007
+ epoch, interv, result={"status": "success"}, checkpoint_id=checkpoint_id
1008
+ )
1009
+ intervention_ids.append(interv_id)
1010
+ except Exception as e:
1011
+ logger.error(f"Actuator error: {e}")
1012
+ # Record failed intervention
1013
+ if self.rollback_manager:
1014
+ for interv in interventions:
1015
+ self.rollback_manager.record_intervention(
1016
+ epoch, interv, result={"status": "error", "message": str(e)}, checkpoint_id=checkpoint_id
1017
+ )
1018
+ # Auto-rollback if enabled
1019
+ if self.auto_rollback_on_error and self.rollback_manager:
1020
+ logger.warning("Auto-rolling back due to actuator error")
1021
+ self.rollback_manager.rollback(epoch, len(interventions), self.actuator_registry)
1022
+ elif self.actuator_registry:
1023
+ # Use actuator registry
1024
+ try:
1025
+ result = self.actuator_registry.execute(interventions, transaction=True)
1026
+ # Record interventions
1027
+ if self.rollback_manager:
1028
+ for i, interv in enumerate(interventions):
1029
+ interv_result = result.get("results", [{}])[i] if i < len(result.get("results", [])) else {}
1030
+ interv_id = self.rollback_manager.record_intervention(
1031
+ epoch, interv, result=interv_result, checkpoint_id=checkpoint_id
1032
+ )
1033
+ intervention_ids.append(interv_id)
1034
+
1035
+ if result.get("status") == "error":
1036
+ logger.error(f"Actuator registry execution failed: {result.get('message')}")
1037
+ # Auto-rollback if enabled
1038
+ if self.auto_rollback_on_error and self.rollback_manager:
1039
+ logger.warning("Auto-rolling back due to actuator registry error")
1040
+ self.rollback_manager.rollback(epoch, len(interventions), self.actuator_registry)
1041
+ except Exception as e:
1042
+ logger.error(f"Actuator registry error: {e}")
1043
+ # Auto-rollback if enabled
1044
+ if self.auto_rollback_on_error and self.rollback_manager:
1045
+ logger.warning("Auto-rolling back due to actuator registry exception")
1046
+ self.rollback_manager.rollback(epoch, len(interventions), self.actuator_registry)
1047
+
1048
+ # Store action for causal effect estimation
1049
+ if interventions:
1050
+ for interv in interventions:
1051
+ self.action_history.append({
1052
+ "lever_id": interv.lever_id,
1053
+ "parameters": interv.parameters
1054
+ })
1055
+
1056
+ # 4. Update (after observing outcome)
1057
+ # For base implementation, we'll simulate outcome
1058
+ # In real implementation, this would come from sensor after action
1059
+ if sensor_provider:
1060
+ outcome_snapshot = sensor_provider() # Get outcome
1061
+ else:
1062
+ # Simulate outcome (simplified)
1063
+ outcome_snapshot = snapshot.copy()
1064
+ for interv in interventions:
1065
+ # Simple effect simulation
1066
+ for metric_name in outcome_snapshot:
1067
+ outcome_snapshot[metric_name] += 0.1 * len(interventions)
1068
+
1069
+ # Record outcome
1070
+ outcome_event = OutcomeEvent(
1071
+ epoch=epoch,
1072
+ metrics=outcome_snapshot,
1073
+ timestamp=datetime.now(timezone.utc).isoformat()
1074
+ )
1075
+ ledger_event = LedgerEvent.from_outcome(outcome_event)
1076
+ self.ledger.append(ledger_event)
1077
+
1078
+ # Compute deltas
1079
+ deltas = {name: outcome_snapshot.get(name, 0.0) - snapshot.get(name, 0.0) for name in self.doctrine.metrics.keys()}
1080
+ self.metric_delta_history.append(deltas)
1081
+ if len(self.metric_delta_history) > self.max_history:
1082
+ self.metric_delta_history.pop(0)
1083
+
1084
+ # Extract outcome features
1085
+ x_tp1 = self.extract_features(outcome_snapshot)
1086
+
1087
+ # Encode action for transition model
1088
+ if interventions:
1089
+ u_t = self._encode_action({
1090
+ "lever_id": interventions[0].lever_id,
1091
+ "parameters": interventions[0].parameters
1092
+ })
1093
+ else:
1094
+ u_t = np.zeros(len(x_t))
1095
+
1096
+ # Update transition model
1097
+ self.update_transition_model(x_t, u_t, x_tp1)
1098
+
1099
+ # Update causal effects
1100
+ self.update_causal_effects(epoch)
1101
+
1102
+ # Update drift detection
1103
+ # Extract just the instantaneous metrics for comparison
1104
+ n_metrics = len(self.doctrine.metrics)
1105
+ x_tp1_metrics = x_tp1[:n_metrics] if len(x_tp1) >= n_metrics else x_tp1
1106
+ x_pred_metrics, _ = self.predict_next_state(x_t, u_t)
1107
+ self.update_drift_detection(x_tp1_metrics, x_pred_metrics)
1108
+
1109
+ return {
1110
+ "epoch": epoch,
1111
+ "observation": snapshot,
1112
+ "interventions": [interv.model_dump() for interv in interventions],
1113
+ "outcome": outcome_snapshot,
1114
+ "deltas": deltas,
1115
+ "score": score,
1116
+ "rationale": rationale,
1117
+ "decision_hash": decision_hash,
1118
+ "explanation": explanation,
1119
+ "conservative_mode": self.conservative_mode,
1120
+ "cusum_stat": self.cusum_stat,
1121
+ "checkpoint_id": checkpoint_id,
1122
+ "intervention_ids": intervention_ids
1123
+ }
1124
+
1125
+ def rollback_last_n(self, epoch: int, n: int) -> List[str]:
1126
+ """
1127
+ Rollback last N interventions.
1128
+
1129
+ Args:
1130
+ epoch: Current epoch
1131
+ n: Number of interventions to rollback
1132
+
1133
+ Returns:
1134
+ List[str]: List of intervention IDs that were rolled back
1135
+ """
1136
+ if not self.rollback_manager:
1137
+ logger.warning("Rollback manager not initialized")
1138
+ return []
1139
+
1140
+ return self.rollback_manager.rollback(epoch, n, self.actuator_registry)
1141
+
1142
+ def enable_rollback(
1143
+ self,
1144
+ db_path: str = ":memory:",
1145
+ retention_days: int = 7,
1146
+ auto_rollback_on_error: bool = False,
1147
+ auto_rollback_on_invariant: bool = False
1148
+ ) -> None:
1149
+ """
1150
+ Enable rollback functionality.
1151
+
1152
+ Args:
1153
+ db_path: Path to rollback database
1154
+ retention_days: Number of days to retain checkpoints
1155
+ auto_rollback_on_error: Auto-rollback on actuator errors
1156
+ auto_rollback_on_invariant: Auto-rollback on invariant violations
1157
+ """
1158
+ if ROLLBACK_AVAILABLE and RollbackManager:
1159
+ try:
1160
+ self.rollback_manager = RollbackManager(db_path, retention_days)
1161
+ self.auto_rollback_on_error = auto_rollback_on_error
1162
+ self.auto_rollback_on_invariant = auto_rollback_on_invariant
1163
+ logger.info("Rollback functionality enabled")
1164
+ except Exception as e:
1165
+ logger.error(f"Failed to enable rollback: {e}")
1166
+ else:
1167
+ logger.warning("Rollback not available (utils.rollback not importable)")
1168
+