ciris-agent 1.7.7__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 (986) hide show
  1. ciris_adapters/README.md +113 -0
  2. ciris_adapters/__init__.py +30 -0
  3. ciris_adapters/ciris_covenant_metrics/README.md +144 -0
  4. ciris_adapters/ciris_covenant_metrics/__init__.py +36 -0
  5. ciris_adapters/ciris_covenant_metrics/adapter.py +249 -0
  6. ciris_adapters/ciris_covenant_metrics/manifest.json +152 -0
  7. ciris_adapters/ciris_covenant_metrics/services.py +403 -0
  8. ciris_adapters/ciris_hosted_tools/__init__.py +24 -0
  9. ciris_adapters/ciris_hosted_tools/adapter.py +169 -0
  10. ciris_adapters/ciris_hosted_tools/manifest.json +94 -0
  11. ciris_adapters/ciris_hosted_tools/services.py +744 -0
  12. ciris_adapters/external_data_sql/README.md +559 -0
  13. ciris_adapters/external_data_sql/__init__.py +43 -0
  14. ciris_adapters/external_data_sql/adapter.py +144 -0
  15. ciris_adapters/external_data_sql/configurable.py +315 -0
  16. ciris_adapters/external_data_sql/dialects/__init__.py +37 -0
  17. ciris_adapters/external_data_sql/dialects/base.py +133 -0
  18. ciris_adapters/external_data_sql/dialects/mysql.py +63 -0
  19. ciris_adapters/external_data_sql/dialects/postgresql.py +59 -0
  20. ciris_adapters/external_data_sql/dialects/sqlite.py +62 -0
  21. ciris_adapters/external_data_sql/example_config.json +88 -0
  22. ciris_adapters/external_data_sql/example_privacy_schema.yaml +127 -0
  23. ciris_adapters/external_data_sql/manifest.json +195 -0
  24. ciris_adapters/external_data_sql/privacy_schema_loader.py +189 -0
  25. ciris_adapters/external_data_sql/protocol.py +101 -0
  26. ciris_adapters/external_data_sql/schemas.py +146 -0
  27. ciris_adapters/external_data_sql/service.py +1547 -0
  28. ciris_adapters/external_data_sql/service_old.py +492 -0
  29. ciris_adapters/home_assistant/__init__.py +63 -0
  30. ciris_adapters/home_assistant/adapter.py +201 -0
  31. ciris_adapters/home_assistant/communication_service.py +347 -0
  32. ciris_adapters/home_assistant/configurable.py +667 -0
  33. ciris_adapters/home_assistant/manifest.json +203 -0
  34. ciris_adapters/home_assistant/schemas.py +129 -0
  35. ciris_adapters/home_assistant/service.py +751 -0
  36. ciris_adapters/home_assistant/tool_service.py +441 -0
  37. ciris_adapters/mcp_client/__init__.py +82 -0
  38. ciris_adapters/mcp_client/adapter.py +847 -0
  39. ciris_adapters/mcp_client/config.py +280 -0
  40. ciris_adapters/mcp_client/configurable.py +422 -0
  41. ciris_adapters/mcp_client/manifest.json +185 -0
  42. ciris_adapters/mcp_client/mcp_communication_service.py +393 -0
  43. ciris_adapters/mcp_client/mcp_tool_service.py +463 -0
  44. ciris_adapters/mcp_client/mcp_wise_service.py +394 -0
  45. ciris_adapters/mcp_client/schemas.py +149 -0
  46. ciris_adapters/mcp_client/security.py +592 -0
  47. ciris_adapters/mcp_common/__init__.py +44 -0
  48. ciris_adapters/mcp_common/manifest.json +25 -0
  49. ciris_adapters/mcp_common/protocol.py +315 -0
  50. ciris_adapters/mcp_common/schemas.py +225 -0
  51. ciris_adapters/mcp_server/__init__.py +47 -0
  52. ciris_adapters/mcp_server/adapter.py +581 -0
  53. ciris_adapters/mcp_server/config.py +260 -0
  54. ciris_adapters/mcp_server/configurable.py +393 -0
  55. ciris_adapters/mcp_server/handlers.py +663 -0
  56. ciris_adapters/mcp_server/manifest.json +211 -0
  57. ciris_adapters/mcp_server/security.py +500 -0
  58. ciris_adapters/mock_llm/README.md +117 -0
  59. ciris_adapters/mock_llm/__init__.py +21 -0
  60. ciris_adapters/mock_llm/adapter.py +131 -0
  61. ciris_adapters/mock_llm/configurable.py +237 -0
  62. ciris_adapters/mock_llm/manifest.json +106 -0
  63. ciris_adapters/mock_llm/protocol.py +37 -0
  64. ciris_adapters/mock_llm/responses.py +520 -0
  65. ciris_adapters/mock_llm/responses_action_selection.py +1041 -0
  66. ciris_adapters/mock_llm/responses_epistemic.py +17 -0
  67. ciris_adapters/mock_llm/responses_feedback.py +27 -0
  68. ciris_adapters/mock_llm/schemas.py +35 -0
  69. ciris_adapters/mock_llm/service.py +294 -0
  70. ciris_adapters/navigation/__init__.py +21 -0
  71. ciris_adapters/navigation/adapter.py +129 -0
  72. ciris_adapters/navigation/configurable.py +239 -0
  73. ciris_adapters/navigation/manifest.json +104 -0
  74. ciris_adapters/navigation/service.py +487 -0
  75. ciris_adapters/reddit/README.md +132 -0
  76. ciris_adapters/reddit/REDDIT_ADAPTER_ANALYSIS.md +715 -0
  77. ciris_adapters/reddit/REDDIT_ADAPTER_SUMMARY.txt +278 -0
  78. ciris_adapters/reddit/REDDIT_ANALYSIS_INDEX.md +307 -0
  79. ciris_adapters/reddit/REDDIT_PRODUCTION_READINESS_PLAN.md +518 -0
  80. ciris_adapters/reddit/__init__.py +15 -0
  81. ciris_adapters/reddit/adapter.py +189 -0
  82. ciris_adapters/reddit/configurable.py +274 -0
  83. ciris_adapters/reddit/error_handler.py +307 -0
  84. ciris_adapters/reddit/manifest.json +218 -0
  85. ciris_adapters/reddit/observer.py +532 -0
  86. ciris_adapters/reddit/protocol.py +34 -0
  87. ciris_adapters/reddit/schemas.py +433 -0
  88. ciris_adapters/reddit/service.py +1471 -0
  89. ciris_adapters/sample_adapter/README.md +474 -0
  90. ciris_adapters/sample_adapter/__init__.py +45 -0
  91. ciris_adapters/sample_adapter/adapter.py +208 -0
  92. ciris_adapters/sample_adapter/configurable.py +469 -0
  93. ciris_adapters/sample_adapter/manifest.json +247 -0
  94. ciris_adapters/sample_adapter/services.py +486 -0
  95. ciris_adapters/weather/__init__.py +16 -0
  96. ciris_adapters/weather/adapter.py +130 -0
  97. ciris_adapters/weather/configurable.py +240 -0
  98. ciris_adapters/weather/manifest.json +156 -0
  99. ciris_adapters/weather/service.py +600 -0
  100. ciris_agent-1.7.7.dist-info/METADATA +284 -0
  101. ciris_agent-1.7.7.dist-info/RECORD +986 -0
  102. ciris_agent-1.7.7.dist-info/WHEEL +5 -0
  103. ciris_agent-1.7.7.dist-info/entry_points.txt +15 -0
  104. ciris_agent-1.7.7.dist-info/licenses/LICENSE +205 -0
  105. ciris_agent-1.7.7.dist-info/licenses/NOTICE +82 -0
  106. ciris_agent-1.7.7.dist-info/top_level.txt +4 -0
  107. ciris_engine/__init__.py +15 -0
  108. ciris_engine/ciris_templates/ally.yaml +632 -0
  109. ciris_engine/ciris_templates/default.yaml +411 -0
  110. ciris_engine/ciris_templates/echo-core.yaml +629 -0
  111. ciris_engine/ciris_templates/echo-speculative.yaml +764 -0
  112. ciris_engine/ciris_templates/echo.yaml +647 -0
  113. ciris_engine/ciris_templates/sage.yaml +332 -0
  114. ciris_engine/ciris_templates/scout.yaml +338 -0
  115. ciris_engine/ciris_templates/test.yaml +168 -0
  116. ciris_engine/cli.py +42 -0
  117. ciris_engine/config/CIRIS_SERVICES.json +19 -0
  118. ciris_engine/config/MODEL_CAPABILITIES.json +419 -0
  119. ciris_engine/config/PRICING_DATA.json +179 -0
  120. ciris_engine/config/__init__.py +50 -0
  121. ciris_engine/config/ciris_services.py +113 -0
  122. ciris_engine/config/model_capabilities.py +388 -0
  123. ciris_engine/config/pricing_models.py +276 -0
  124. ciris_engine/constants.py +35 -0
  125. ciris_engine/data/__init__.py +1 -0
  126. ciris_engine/data/covenant_1.0b.txt +978 -0
  127. ciris_engine/gui_static/11steps.svg +107 -0
  128. ciris_engine/gui_static/2x-schematics.png +0 -0
  129. ciris_engine/gui_static/404/index.html +1 -0
  130. ciris_engine/gui_static/404.html +1 -0
  131. ciris_engine/gui_static/_next/static/0edhkwDxd5UccTsCmtaBi/_buildManifest.js +1 -0
  132. ciris_engine/gui_static/_next/static/0edhkwDxd5UccTsCmtaBi/_ssgManifest.js +1 -0
  133. ciris_engine/gui_static/_next/static/U-3xTQao7hc2wnAi-Uekm/_buildManifest.js +1 -0
  134. ciris_engine/gui_static/_next/static/U-3xTQao7hc2wnAi-Uekm/_ssgManifest.js +1 -0
  135. ciris_engine/gui_static/_next/static/chunks/3297-60e86ba0f8a7b040.js +1 -0
  136. ciris_engine/gui_static/_next/static/chunks/3835-2aad4b7f5f8e4643.js +1 -0
  137. ciris_engine/gui_static/_next/static/chunks/4499-99a0bc47de0b8975.js +1 -0
  138. ciris_engine/gui_static/_next/static/chunks/4534-af88cd4ba6e99bff.js +1 -0
  139. ciris_engine/gui_static/_next/static/chunks/4541-84b455f9e0dc4cfe.js +1 -0
  140. ciris_engine/gui_static/_next/static/chunks/4789-61412711484754bb.js +1 -0
  141. ciris_engine/gui_static/_next/static/chunks/6539-c6398bc9d7018430.js +1 -0
  142. ciris_engine/gui_static/_next/static/chunks/704-8e827b26cc8c2d32.js +1 -0
  143. ciris_engine/gui_static/_next/static/chunks/704-fb45d630f3192c6f.js +1 -0
  144. ciris_engine/gui_static/_next/static/chunks/8072-de4952a2e6d2b33f.js +1 -0
  145. ciris_engine/gui_static/_next/static/chunks/8315-b91d03a3949db0af.js +1 -0
  146. ciris_engine/gui_static/_next/static/chunks/8386-f93a83ccbd789bd9.js +1 -0
  147. ciris_engine/gui_static/_next/static/chunks/87c73c54-781a7f35148d5433.js +1 -0
  148. ciris_engine/gui_static/_next/static/chunks/8903-fefea3339a02d41b.js +1 -0
  149. ciris_engine/gui_static/_next/static/chunks/9090-e66485adf8d9d990.js +1 -0
  150. ciris_engine/gui_static/_next/static/chunks/app/_not-found/page-a67d9808462c23b1.js +1 -0
  151. ciris_engine/gui_static/_next/static/chunks/app/account/api-keys/page-2d7ee1583bbbd02e.js +1 -0
  152. ciris_engine/gui_static/_next/static/chunks/app/account/api-keys/page-6a3c2bae6fe92b7b.js +1 -0
  153. ciris_engine/gui_static/_next/static/chunks/app/account/consent/page-2ed3a035136bc4e8.js +1 -0
  154. ciris_engine/gui_static/_next/static/chunks/app/account/consent/page-b2f5c91844a32422.js +1 -0
  155. ciris_engine/gui_static/_next/static/chunks/app/account/page-25b90f89af3ea58c.js +1 -0
  156. ciris_engine/gui_static/_next/static/chunks/app/account/page-b65d16c94ecaf69c.js +1 -0
  157. ciris_engine/gui_static/_next/static/chunks/app/account/privacy/page-675b6d05c8f9184f.js +1 -0
  158. ciris_engine/gui_static/_next/static/chunks/app/account/privacy/page-cbee2e1c8ab52145.js +1 -0
  159. ciris_engine/gui_static/_next/static/chunks/app/account/settings/page-0f44da06697cf9f0.js +1 -0
  160. ciris_engine/gui_static/_next/static/chunks/app/account/settings/page-563420253577edbf.js +1 -0
  161. ciris_engine/gui_static/_next/static/chunks/app/adapters/page-1854631018bc32be.js +1 -0
  162. ciris_engine/gui_static/_next/static/chunks/app/agents/page-8353752c176a7c70.js +1 -0
  163. ciris_engine/gui_static/_next/static/chunks/app/agents/page-f61a529f110a6040.js +1 -0
  164. ciris_engine/gui_static/_next/static/chunks/app/api-demo/page-7f19b9d20d39be28.js +1 -0
  165. ciris_engine/gui_static/_next/static/chunks/app/api-demo/page-d1063938f249b8bd.js +1 -0
  166. ciris_engine/gui_static/_next/static/chunks/app/audit/page-321b6728b8fff0bb.js +1 -0
  167. ciris_engine/gui_static/_next/static/chunks/app/audit/page-ebac35ca961a1277.js +1 -0
  168. ciris_engine/gui_static/_next/static/chunks/app/billing/page-6f3dc3bd02924f8e.js +1 -0
  169. ciris_engine/gui_static/_next/static/chunks/app/billing/page-fa4a469f814c821a.js +1 -0
  170. ciris_engine/gui_static/_next/static/chunks/app/comms/page-0d4f734269addd8f.js +1 -0
  171. ciris_engine/gui_static/_next/static/chunks/app/comms/page-79227d426050089c.js +1 -0
  172. ciris_engine/gui_static/_next/static/chunks/app/config/page-018d21d683b6e5bc.js +1 -0
  173. ciris_engine/gui_static/_next/static/chunks/app/config/page-2aa5a5363ca2a371.js +1 -0
  174. ciris_engine/gui_static/_next/static/chunks/app/consent/page-198373205fd316e2.js +1 -0
  175. ciris_engine/gui_static/_next/static/chunks/app/consent/page-f2ca39e7713b13f8.js +1 -0
  176. ciris_engine/gui_static/_next/static/chunks/app/dashboard/page-1dd5a196f643c60d.js +1 -0
  177. ciris_engine/gui_static/_next/static/chunks/app/dashboard/page-530a04d3abbb8cda.js +1 -0
  178. ciris_engine/gui_static/_next/static/chunks/app/docs/page-3193b06d094ab654.js +1 -0
  179. ciris_engine/gui_static/_next/static/chunks/app/docs/page-330e996dedb87aba.js +1 -0
  180. ciris_engine/gui_static/_next/static/chunks/app/layout-0a70f5fc460298b1.js +1 -0
  181. ciris_engine/gui_static/_next/static/chunks/app/layout-21f2f99dd5b336e9.js +1 -0
  182. ciris_engine/gui_static/_next/static/chunks/app/login/page-33240e6c6034a49d.js +1 -0
  183. ciris_engine/gui_static/_next/static/chunks/app/login/page-68ffab6d54a7fdcd.js +1 -0
  184. ciris_engine/gui_static/_next/static/chunks/app/logs/page-8a6167aecc4a475c.js +1 -0
  185. ciris_engine/gui_static/_next/static/chunks/app/memory/page-9ca8c5d0056de3ff.js +1 -0
  186. ciris_engine/gui_static/_next/static/chunks/app/memory/page-e961226941c18f81.js +1 -0
  187. ciris_engine/gui_static/_next/static/chunks/app/page-6fdb065a787a4974.js +1 -0
  188. ciris_engine/gui_static/_next/static/chunks/app/page-89f87d431be6064a.js +1 -0
  189. ciris_engine/gui_static/_next/static/chunks/app/runtime/page-2e728b9c43aa164d.js +1 -0
  190. ciris_engine/gui_static/_next/static/chunks/app/runtime/page-c7dd033dc40a72f0.js +1 -0
  191. ciris_engine/gui_static/_next/static/chunks/app/services/page-ae9f0bdf11d01a95.js +1 -0
  192. ciris_engine/gui_static/_next/static/chunks/app/services/page-b10feb79ca5d75e5.js +1 -0
  193. ciris_engine/gui_static/_next/static/chunks/app/sessions/page-13ebe7ef1c16ae11.js +1 -0
  194. ciris_engine/gui_static/_next/static/chunks/app/sessions/page-e6c82b16d617f785.js +1 -0
  195. ciris_engine/gui_static/_next/static/chunks/app/setup/page-0beb5f5b5a5c20fc.js +1 -0
  196. ciris_engine/gui_static/_next/static/chunks/app/setup/page-2595e729eae30c0e.js +1 -0
  197. ciris_engine/gui_static/_next/static/chunks/app/status-dashboard/page-1037c987aecc3653.js +1 -0
  198. ciris_engine/gui_static/_next/static/chunks/app/status-dashboard/page-2ffd147f6d3162ff.js +1 -0
  199. ciris_engine/gui_static/_next/static/chunks/app/system/page-2c5798d58cafcd91.js +1 -0
  200. ciris_engine/gui_static/_next/static/chunks/app/system/page-505b1ba4eceb01c3.js +1 -0
  201. ciris_engine/gui_static/_next/static/chunks/app/test-auth/page-b0cad31d5cb1b2fa.js +1 -0
  202. ciris_engine/gui_static/_next/static/chunks/app/test-auth/page-f3ecd7a8012df230.js +1 -0
  203. ciris_engine/gui_static/_next/static/chunks/app/test-login/page-f35117fdc4105801.js +1 -0
  204. ciris_engine/gui_static/_next/static/chunks/app/test-login/page-fb583a7924114906.js +1 -0
  205. ciris_engine/gui_static/_next/static/chunks/app/test-sdk/page-50f116fd76935563.js +1 -0
  206. ciris_engine/gui_static/_next/static/chunks/app/test-sdk/page-c37d8aa5ba623a44.js +1 -0
  207. ciris_engine/gui_static/_next/static/chunks/app/tools/page-429aec7a707777ef.js +1 -0
  208. ciris_engine/gui_static/_next/static/chunks/app/tools/page-5f705aad60e0c04e.js +1 -0
  209. ciris_engine/gui_static/_next/static/chunks/app/users/page-13476b8b0f3808cc.js +1 -0
  210. ciris_engine/gui_static/_next/static/chunks/app/users/page-7e500d154ed5bba4.js +1 -0
  211. ciris_engine/gui_static/_next/static/chunks/app/wa/page-cc4a9d8a5cb44d08.js +1 -0
  212. ciris_engine/gui_static/_next/static/chunks/app/wa/page-ec3e429efbc79230.js +1 -0
  213. ciris_engine/gui_static/_next/static/chunks/framework-9d29490f5ba089ba.js +1 -0
  214. ciris_engine/gui_static/_next/static/chunks/main-1f554952e47a82c4.js +1 -0
  215. ciris_engine/gui_static/_next/static/chunks/main-app-26fa8aed029082e5.js +1 -0
  216. ciris_engine/gui_static/_next/static/chunks/main-app-97b0486ef6bcef25.js +1 -0
  217. ciris_engine/gui_static/_next/static/chunks/pages/_app-6ce685456e616eb2.js +1 -0
  218. ciris_engine/gui_static/_next/static/chunks/pages/_error-d4bce98d93fe21e7.js +1 -0
  219. ciris_engine/gui_static/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
  220. ciris_engine/gui_static/_next/static/chunks/webpack-fcebd240b7f8477d.js +1 -0
  221. ciris_engine/gui_static/_next/static/css/16b94b1fe0cc6e37.css +3 -0
  222. ciris_engine/gui_static/_next/static/css/77a24ceaae86deff.css +3 -0
  223. ciris_engine/gui_static/_next/static/media/4cf2300e9c8272f7-s.p.woff2 +0 -0
  224. ciris_engine/gui_static/_next/static/media/747892c23ea88013-s.woff2 +0 -0
  225. ciris_engine/gui_static/_next/static/media/8d697b304b401681-s.woff2 +0 -0
  226. ciris_engine/gui_static/_next/static/media/93f479601ee12b01-s.p.woff2 +0 -0
  227. ciris_engine/gui_static/_next/static/media/9610d9e46709d722-s.woff2 +0 -0
  228. ciris_engine/gui_static/_next/static/media/ba015fad6dcf6784-s.woff2 +0 -0
  229. ciris_engine/gui_static/_next/static/media/d8298875641ec7d4-s.p.woff2 +0 -0
  230. ciris_engine/gui_static/account/api-keys/index.html +1 -0
  231. ciris_engine/gui_static/account/api-keys/index.txt +27 -0
  232. ciris_engine/gui_static/account/consent/index.html +1 -0
  233. ciris_engine/gui_static/account/consent/index.txt +27 -0
  234. ciris_engine/gui_static/account/index.html +1 -0
  235. ciris_engine/gui_static/account/index.txt +27 -0
  236. ciris_engine/gui_static/account/privacy/index.html +1 -0
  237. ciris_engine/gui_static/account/privacy/index.txt +27 -0
  238. ciris_engine/gui_static/account/settings/index.html +1 -0
  239. ciris_engine/gui_static/account/settings/index.txt +27 -0
  240. ciris_engine/gui_static/adapters/index.html +1 -0
  241. ciris_engine/gui_static/adapters/index.txt +27 -0
  242. ciris_engine/gui_static/agents/index.html +1 -0
  243. ciris_engine/gui_static/agents/index.txt +27 -0
  244. ciris_engine/gui_static/andrew-roberts-euBRXcx57T4-unsplash.jpg +0 -0
  245. ciris_engine/gui_static/api-demo/index.html +1 -0
  246. ciris_engine/gui_static/api-demo/index.txt +27 -0
  247. ciris_engine/gui_static/audit/index.html +1 -0
  248. ciris_engine/gui_static/audit/index.txt +27 -0
  249. ciris_engine/gui_static/billing/index.html +1 -0
  250. ciris_engine/gui_static/billing/index.txt +27 -0
  251. ciris_engine/gui_static/blurryinfo.png +0 -0
  252. ciris_engine/gui_static/chip-vincent-PkQDwfl9Flc-unsplash.jpg +0 -0
  253. ciris_engine/gui_static/ciris-architecture.svg +338 -0
  254. ciris_engine/gui_static/comms/index.html +1 -0
  255. ciris_engine/gui_static/comms/index.txt +27 -0
  256. ciris_engine/gui_static/config/index.html +1 -0
  257. ciris_engine/gui_static/config/index.txt +27 -0
  258. ciris_engine/gui_static/consent/index.html +1 -0
  259. ciris_engine/gui_static/consent/index.txt +27 -0
  260. ciris_engine/gui_static/dashboard/index.html +1 -0
  261. ciris_engine/gui_static/dashboard/index.txt +27 -0
  262. ciris_engine/gui_static/docs/index.html +1 -0
  263. ciris_engine/gui_static/docs/index.txt +27 -0
  264. ciris_engine/gui_static/eric.png +0 -0
  265. ciris_engine/gui_static/file.svg +1 -0
  266. ciris_engine/gui_static/globe.svg +1 -0
  267. ciris_engine/gui_static/index.html +1 -0
  268. ciris_engine/gui_static/index.txt +27 -0
  269. ciris_engine/gui_static/infogfx-1@2x.png +0 -0
  270. ciris_engine/gui_static/infogfx-2.png +0 -0
  271. ciris_engine/gui_static/infogfx-dark-1.png +0 -0
  272. ciris_engine/gui_static/kelly-vohs-soSTXmIxTDU-unsplash.jpg +0 -0
  273. ciris_engine/gui_static/login/index.html +1 -0
  274. ciris_engine/gui_static/login/index.txt +27 -0
  275. ciris_engine/gui_static/logs/index.html +1 -0
  276. ciris_engine/gui_static/logs/index.txt +27 -0
  277. ciris_engine/gui_static/memory/index.html +1 -0
  278. ciris_engine/gui_static/memory/index.txt +27 -0
  279. ciris_engine/gui_static/nathan-farrish-ArcTfEoBgzs-unsplash.jpg +0 -0
  280. ciris_engine/gui_static/next.svg +1 -0
  281. ciris_engine/gui_static/overview.svg +512 -0
  282. ciris_engine/gui_static/overview1.svg +407 -0
  283. ciris_engine/gui_static/overview2.svg +370 -0
  284. ciris_engine/gui_static/pipeline-visualization.svg +278 -0
  285. ciris_engine/gui_static/privacy-policy.html +160 -0
  286. ciris_engine/gui_static/runtime/index.html +8 -0
  287. ciris_engine/gui_static/runtime/index.txt +27 -0
  288. ciris_engine/gui_static/services/index.html +1 -0
  289. ciris_engine/gui_static/services/index.txt +27 -0
  290. ciris_engine/gui_static/sessions/index.html +1 -0
  291. ciris_engine/gui_static/sessions/index.txt +27 -0
  292. ciris_engine/gui_static/setup/index.html +1 -0
  293. ciris_engine/gui_static/setup/index.txt +27 -0
  294. ciris_engine/gui_static/status-dashboard/index.html +1 -0
  295. ciris_engine/gui_static/status-dashboard/index.txt +27 -0
  296. ciris_engine/gui_static/system/index.html +1 -0
  297. ciris_engine/gui_static/system/index.txt +27 -0
  298. ciris_engine/gui_static/terms-of-service.html +174 -0
  299. ciris_engine/gui_static/test-auth/index.html +1 -0
  300. ciris_engine/gui_static/test-auth/index.txt +27 -0
  301. ciris_engine/gui_static/test-login/index.html +1 -0
  302. ciris_engine/gui_static/test-login/index.txt +27 -0
  303. ciris_engine/gui_static/test-sdk/index.html +1 -0
  304. ciris_engine/gui_static/test-sdk/index.txt +27 -0
  305. ciris_engine/gui_static/tools/index.html +1 -0
  306. ciris_engine/gui_static/tools/index.txt +27 -0
  307. ciris_engine/gui_static/users/index.html +1 -0
  308. ciris_engine/gui_static/users/index.txt +27 -0
  309. ciris_engine/gui_static/vercel.svg +1 -0
  310. ciris_engine/gui_static/videos/video1.mp4 +0 -0
  311. ciris_engine/gui_static/videos/video3.mp4 +0 -0
  312. ciris_engine/gui_static/wa/index.html +1 -0
  313. ciris_engine/gui_static/wa/index.txt +27 -0
  314. ciris_engine/gui_static/window.svg +1 -0
  315. ciris_engine/logic/__init__.py +8 -0
  316. ciris_engine/logic/adapters/__init__.py +74 -0
  317. ciris_engine/logic/adapters/api/__init__.py +5 -0
  318. ciris_engine/logic/adapters/api/adapter.py +1037 -0
  319. ciris_engine/logic/adapters/api/api_communication.py +370 -0
  320. ciris_engine/logic/adapters/api/api_document.py +330 -0
  321. ciris_engine/logic/adapters/api/api_observer.py +24 -0
  322. ciris_engine/logic/adapters/api/api_runtime_control.py +388 -0
  323. ciris_engine/logic/adapters/api/api_tools.py +299 -0
  324. ciris_engine/logic/adapters/api/api_vision.py +215 -0
  325. ciris_engine/logic/adapters/api/app.py +272 -0
  326. ciris_engine/logic/adapters/api/auth.py +159 -0
  327. ciris_engine/logic/adapters/api/config.py +101 -0
  328. ciris_engine/logic/adapters/api/constants.py +55 -0
  329. ciris_engine/logic/adapters/api/dependencies/__init__.py +1 -0
  330. ciris_engine/logic/adapters/api/dependencies/auth.py +260 -0
  331. ciris_engine/logic/adapters/api/endpoints/__init__.py +1 -0
  332. ciris_engine/logic/adapters/api/endpoints/emergency.py +86 -0
  333. ciris_engine/logic/adapters/api/middleware/__init__.py +1 -0
  334. ciris_engine/logic/adapters/api/middleware/rate_limiter.py +302 -0
  335. ciris_engine/logic/adapters/api/models.py +29 -0
  336. ciris_engine/logic/adapters/api/routes/__init__.py +52 -0
  337. ciris_engine/logic/adapters/api/routes/agent.py +1762 -0
  338. ciris_engine/logic/adapters/api/routes/audit.py +707 -0
  339. ciris_engine/logic/adapters/api/routes/auth.py +1745 -0
  340. ciris_engine/logic/adapters/api/routes/billing.py +895 -0
  341. ciris_engine/logic/adapters/api/routes/config.py +329 -0
  342. ciris_engine/logic/adapters/api/routes/connectors.py +534 -0
  343. ciris_engine/logic/adapters/api/routes/consent.py +637 -0
  344. ciris_engine/logic/adapters/api/routes/dsar.py +637 -0
  345. ciris_engine/logic/adapters/api/routes/dsar_multi_source.py +484 -0
  346. ciris_engine/logic/adapters/api/routes/emergency.py +302 -0
  347. ciris_engine/logic/adapters/api/routes/memory.py +733 -0
  348. ciris_engine/logic/adapters/api/routes/memory_filters.py +230 -0
  349. ciris_engine/logic/adapters/api/routes/memory_models.py +112 -0
  350. ciris_engine/logic/adapters/api/routes/memory_queries.py +236 -0
  351. ciris_engine/logic/adapters/api/routes/memory_query_helpers.py +394 -0
  352. ciris_engine/logic/adapters/api/routes/memory_visualization.py +359 -0
  353. ciris_engine/logic/adapters/api/routes/memory_visualization_helpers.py +110 -0
  354. ciris_engine/logic/adapters/api/routes/partnership.py +541 -0
  355. ciris_engine/logic/adapters/api/routes/setup.py +1374 -0
  356. ciris_engine/logic/adapters/api/routes/system.py +3049 -0
  357. ciris_engine/logic/adapters/api/routes/system_extensions.py +952 -0
  358. ciris_engine/logic/adapters/api/routes/telemetry.py +1987 -0
  359. ciris_engine/logic/adapters/api/routes/telemetry_converters.py +141 -0
  360. ciris_engine/logic/adapters/api/routes/telemetry_helpers.py +111 -0
  361. ciris_engine/logic/adapters/api/routes/telemetry_logs_reader.py +280 -0
  362. ciris_engine/logic/adapters/api/routes/telemetry_metrics.py +131 -0
  363. ciris_engine/logic/adapters/api/routes/telemetry_models.py +190 -0
  364. ciris_engine/logic/adapters/api/routes/telemetry_otlp.py +878 -0
  365. ciris_engine/logic/adapters/api/routes/telemetry_resource_helpers.py +191 -0
  366. ciris_engine/logic/adapters/api/routes/tickets.py +541 -0
  367. ciris_engine/logic/adapters/api/routes/tools.py +556 -0
  368. ciris_engine/logic/adapters/api/routes/transparency.py +281 -0
  369. ciris_engine/logic/adapters/api/routes/users.py +981 -0
  370. ciris_engine/logic/adapters/api/routes/verification.py +373 -0
  371. ciris_engine/logic/adapters/api/routes/wa.py +369 -0
  372. ciris_engine/logic/adapters/api/service_configuration.py +177 -0
  373. ciris_engine/logic/adapters/api/services/__init__.py +1 -0
  374. ciris_engine/logic/adapters/api/services/auth_service.py +1417 -0
  375. ciris_engine/logic/adapters/api/services/oauth_security.py +68 -0
  376. ciris_engine/logic/adapters/base.py +141 -0
  377. ciris_engine/logic/adapters/base_adapter.py +73 -0
  378. ciris_engine/logic/adapters/base_observer.py +1141 -0
  379. ciris_engine/logic/adapters/base_vision.py +312 -0
  380. ciris_engine/logic/adapters/cirisnode_client.py +307 -0
  381. ciris_engine/logic/adapters/cli/__init__.py +3 -0
  382. ciris_engine/logic/adapters/cli/adapter.py +207 -0
  383. ciris_engine/logic/adapters/cli/cli_adapter.py +902 -0
  384. ciris_engine/logic/adapters/cli/cli_observer.py +268 -0
  385. ciris_engine/logic/adapters/cli/cli_tools.py +427 -0
  386. ciris_engine/logic/adapters/cli/cli_wa_service.py +134 -0
  387. ciris_engine/logic/adapters/cli/config.py +73 -0
  388. ciris_engine/logic/adapters/discord/__init__.py +3 -0
  389. ciris_engine/logic/adapters/discord/adapter.py +783 -0
  390. ciris_engine/logic/adapters/discord/ciris_discord_client.py +159 -0
  391. ciris_engine/logic/adapters/discord/config.py +177 -0
  392. ciris_engine/logic/adapters/discord/constants.py +185 -0
  393. ciris_engine/logic/adapters/discord/discord-stubs.pyi +50 -0
  394. ciris_engine/logic/adapters/discord/discord_adapter.py +1584 -0
  395. ciris_engine/logic/adapters/discord/discord_audit.py +150 -0
  396. ciris_engine/logic/adapters/discord/discord_channel_manager.py +351 -0
  397. ciris_engine/logic/adapters/discord/discord_connection_manager.py +313 -0
  398. ciris_engine/logic/adapters/discord/discord_embed_formatter.py +369 -0
  399. ciris_engine/logic/adapters/discord/discord_error_classifier.py +302 -0
  400. ciris_engine/logic/adapters/discord/discord_error_handler.py +316 -0
  401. ciris_engine/logic/adapters/discord/discord_guidance_handler.py +460 -0
  402. ciris_engine/logic/adapters/discord/discord_message_handler.py +207 -0
  403. ciris_engine/logic/adapters/discord/discord_observer.py +670 -0
  404. ciris_engine/logic/adapters/discord/discord_rate_limiter.py +249 -0
  405. ciris_engine/logic/adapters/discord/discord_reaction_handler.py +278 -0
  406. ciris_engine/logic/adapters/discord/discord_tool_handler.py +465 -0
  407. ciris_engine/logic/adapters/discord/discord_tool_service.py +790 -0
  408. ciris_engine/logic/adapters/discord/discord_tools.py +90 -0
  409. ciris_engine/logic/adapters/discord/discord_vision_helper.py +148 -0
  410. ciris_engine/logic/adapters/discord/py.typed +0 -0
  411. ciris_engine/logic/adapters/document_parser.py +320 -0
  412. ciris_engine/logic/audit/__init__.py +10 -0
  413. ciris_engine/logic/audit/hash_chain.py +313 -0
  414. ciris_engine/logic/audit/signature_manager.py +352 -0
  415. ciris_engine/logic/audit/verifier.py +408 -0
  416. ciris_engine/logic/buses/__init__.py +21 -0
  417. ciris_engine/logic/buses/base_bus.py +178 -0
  418. ciris_engine/logic/buses/bus_manager.py +121 -0
  419. ciris_engine/logic/buses/communication_bus.py +387 -0
  420. ciris_engine/logic/buses/llm_bus.py +722 -0
  421. ciris_engine/logic/buses/memory_bus.py +577 -0
  422. ciris_engine/logic/buses/prohibitions.py +502 -0
  423. ciris_engine/logic/buses/runtime_control_bus.py +539 -0
  424. ciris_engine/logic/buses/tool_bus.py +482 -0
  425. ciris_engine/logic/buses/wise_bus.py +684 -0
  426. ciris_engine/logic/config/__init__.py +25 -0
  427. ciris_engine/logic/config/bootstrap.py +255 -0
  428. ciris_engine/logic/config/config_accessor.py +202 -0
  429. ciris_engine/logic/config/db_paths.py +194 -0
  430. ciris_engine/logic/config/env_utils.py +39 -0
  431. ciris_engine/logic/conscience/__init__.py +16 -0
  432. ciris_engine/logic/conscience/build_deferral_package.py +0 -0
  433. ciris_engine/logic/conscience/core.py +688 -0
  434. ciris_engine/logic/conscience/interface.py +33 -0
  435. ciris_engine/logic/conscience/registry.py +76 -0
  436. ciris_engine/logic/conscience/thought_depth_guardrail.py +231 -0
  437. ciris_engine/logic/conscience/updated_status_conscience.py +156 -0
  438. ciris_engine/logic/context/__init__.py +10 -0
  439. ciris_engine/logic/context/batch_context.py +550 -0
  440. ciris_engine/logic/context/builder.py +149 -0
  441. ciris_engine/logic/context/channel_resolution.py +136 -0
  442. ciris_engine/logic/context/secrets_snapshot.py +52 -0
  443. ciris_engine/logic/context/system_snapshot.py +116 -0
  444. ciris_engine/logic/context/system_snapshot_helpers.py +1651 -0
  445. ciris_engine/logic/covenant/__init__.py +33 -0
  446. ciris_engine/logic/covenant/executor.py +303 -0
  447. ciris_engine/logic/covenant/extractor.py +382 -0
  448. ciris_engine/logic/covenant/handler.py +241 -0
  449. ciris_engine/logic/covenant/verifier.py +383 -0
  450. ciris_engine/logic/dma/__init__.py +15 -0
  451. ciris_engine/logic/dma/action_selection/__init__.py +11 -0
  452. ciris_engine/logic/dma/action_selection/action_instruction_generator.py +444 -0
  453. ciris_engine/logic/dma/action_selection/context_builder.py +508 -0
  454. ciris_engine/logic/dma/action_selection/faculty_integration.py +193 -0
  455. ciris_engine/logic/dma/action_selection/special_cases.py +132 -0
  456. ciris_engine/logic/dma/action_selection_pdma.py +365 -0
  457. ciris_engine/logic/dma/base_dma.py +335 -0
  458. ciris_engine/logic/dma/csdma.py +239 -0
  459. ciris_engine/logic/dma/dma_executor.py +575 -0
  460. ciris_engine/logic/dma/dsdma_base.py +410 -0
  461. ciris_engine/logic/dma/exceptions.py +4 -0
  462. ciris_engine/logic/dma/factory.py +150 -0
  463. ciris_engine/logic/dma/pdma.py +120 -0
  464. ciris_engine/logic/dma/prompt_loader.py +189 -0
  465. ciris_engine/logic/dma/prompts/action_selection_pdma.yml +58 -0
  466. ciris_engine/logic/dma/prompts/csdma_common_sense.yml +28 -0
  467. ciris_engine/logic/dma/prompts/dsdma_base.yml +17 -0
  468. ciris_engine/logic/dma/prompts/pdma_ethical.yml +42 -0
  469. ciris_engine/logic/formatters/__init__.py +26 -0
  470. ciris_engine/logic/formatters/crisis_resources.py +80 -0
  471. ciris_engine/logic/formatters/escalation.py +21 -0
  472. ciris_engine/logic/formatters/identity.py +224 -0
  473. ciris_engine/logic/formatters/prompt_blocks.py +64 -0
  474. ciris_engine/logic/formatters/system_snapshot.py +193 -0
  475. ciris_engine/logic/formatters/user_profiles.py +108 -0
  476. ciris_engine/logic/handlers/__init__.py +1 -0
  477. ciris_engine/logic/handlers/control/__init__.py +1 -0
  478. ciris_engine/logic/handlers/control/defer_handler.py +195 -0
  479. ciris_engine/logic/handlers/control/ponder_handler.py +154 -0
  480. ciris_engine/logic/handlers/control/reject_handler.py +81 -0
  481. ciris_engine/logic/handlers/external/__init__.py +1 -0
  482. ciris_engine/logic/handlers/external/observe_handler.py +154 -0
  483. ciris_engine/logic/handlers/external/speak_handler.py +250 -0
  484. ciris_engine/logic/handlers/external/tool_handler.py +148 -0
  485. ciris_engine/logic/handlers/memory/__init__.py +1 -0
  486. ciris_engine/logic/handlers/memory/forget_handler.py +107 -0
  487. ciris_engine/logic/handlers/memory/memorize_handler.py +391 -0
  488. ciris_engine/logic/handlers/memory/recall_handler.py +213 -0
  489. ciris_engine/logic/handlers/terminal/__init__.py +1 -0
  490. ciris_engine/logic/handlers/terminal/task_complete_handler.py +299 -0
  491. ciris_engine/logic/infrastructure/__init__.py +1 -0
  492. ciris_engine/logic/infrastructure/handlers/__init__.py +8 -0
  493. ciris_engine/logic/infrastructure/handlers/action_dispatcher.py +382 -0
  494. ciris_engine/logic/infrastructure/handlers/base_handler.py +450 -0
  495. ciris_engine/logic/infrastructure/handlers/exceptions.py +2 -0
  496. ciris_engine/logic/infrastructure/handlers/handler_registry.py +59 -0
  497. ciris_engine/logic/infrastructure/handlers/helpers.py +55 -0
  498. ciris_engine/logic/infrastructure/step_streaming.py +149 -0
  499. ciris_engine/logic/infrastructure/sub_services/__init__.py +1 -0
  500. ciris_engine/logic/infrastructure/sub_services/identity_variance_monitor.py +1035 -0
  501. ciris_engine/logic/infrastructure/sub_services/pattern_analysis_loop.py +758 -0
  502. ciris_engine/logic/infrastructure/sub_services/wa_cli_bootstrap.py +229 -0
  503. ciris_engine/logic/infrastructure/sub_services/wa_cli_display.py +176 -0
  504. ciris_engine/logic/infrastructure/sub_services/wa_cli_oauth.py +404 -0
  505. ciris_engine/logic/infrastructure/sub_services/wa_cli_wizard.py +181 -0
  506. ciris_engine/logic/persistence/__init__.py +130 -0
  507. ciris_engine/logic/persistence/analytics.py +97 -0
  508. ciris_engine/logic/persistence/db/__init__.py +28 -0
  509. ciris_engine/logic/persistence/db/core.py +520 -0
  510. ciris_engine/logic/persistence/db/dialect.py +380 -0
  511. ciris_engine/logic/persistence/db/execution_helpers.py +216 -0
  512. ciris_engine/logic/persistence/db/migration_runner.py +191 -0
  513. ciris_engine/logic/persistence/db/operations.py +313 -0
  514. ciris_engine/logic/persistence/db/query_builder.py +232 -0
  515. ciris_engine/logic/persistence/db/retry.py +154 -0
  516. ciris_engine/logic/persistence/db/setup.py +18 -0
  517. ciris_engine/logic/persistence/migrations/postgres/001_initial_schema.sql +4 -0
  518. ciris_engine/logic/persistence/migrations/postgres/002_add_retry_status.sql +3 -0
  519. ciris_engine/logic/persistence/migrations/postgres/003_add_task_update_tracking.sql +8 -0
  520. ciris_engine/logic/persistence/migrations/postgres/004_add_occurrence_id.sql +54 -0
  521. ciris_engine/logic/persistence/migrations/postgres/005_add_consolidation_locks.sql +22 -0
  522. ciris_engine/logic/persistence/migrations/postgres/006_add_correlation_id_unique_index.sql +16 -0
  523. ciris_engine/logic/persistence/migrations/postgres/007_add_dsar_tickets.sql +39 -0
  524. ciris_engine/logic/persistence/migrations/postgres/008_rename_to_tickets_add_sop.sql +123 -0
  525. ciris_engine/logic/persistence/migrations/postgres/009_add_ticket_status_columns.sql +39 -0
  526. ciris_engine/logic/persistence/migrations/postgres/010_add_images_to_tasks.sql +5 -0
  527. ciris_engine/logic/persistence/migrations/sqlite/001_initial_schema.sql +357 -0
  528. ciris_engine/logic/persistence/migrations/sqlite/002_add_retry_status.sql +3 -0
  529. ciris_engine/logic/persistence/migrations/sqlite/003_add_task_update_tracking.sql +8 -0
  530. ciris_engine/logic/persistence/migrations/sqlite/004_add_occurrence_id.sql +45 -0
  531. ciris_engine/logic/persistence/migrations/sqlite/005_add_consolidation_locks.sql +22 -0
  532. ciris_engine/logic/persistence/migrations/sqlite/006_add_correlation_id_unique_index.sql +16 -0
  533. ciris_engine/logic/persistence/migrations/sqlite/007_add_dsar_tickets.sql +39 -0
  534. ciris_engine/logic/persistence/migrations/sqlite/008_rename_to_tickets_add_sop.sql +120 -0
  535. ciris_engine/logic/persistence/migrations/sqlite/009_add_ticket_status_columns.sql +129 -0
  536. ciris_engine/logic/persistence/migrations/sqlite/010_add_images_to_tasks.sql +17 -0
  537. ciris_engine/logic/persistence/models/__init__.py +141 -0
  538. ciris_engine/logic/persistence/models/correlations.py +881 -0
  539. ciris_engine/logic/persistence/models/deferral.py +68 -0
  540. ciris_engine/logic/persistence/models/dsar.py +286 -0
  541. ciris_engine/logic/persistence/models/graph.py +362 -0
  542. ciris_engine/logic/persistence/models/identity.py +264 -0
  543. ciris_engine/logic/persistence/models/queue_status.py +139 -0
  544. ciris_engine/logic/persistence/models/tasks.py +1043 -0
  545. ciris_engine/logic/persistence/models/thoughts.py +400 -0
  546. ciris_engine/logic/persistence/models/tickets.py +518 -0
  547. ciris_engine/logic/persistence/stores/__init__.py +13 -0
  548. ciris_engine/logic/persistence/stores/auth_helpers.py +117 -0
  549. ciris_engine/logic/persistence/stores/authentication_store.py +414 -0
  550. ciris_engine/logic/persistence/utils.py +212 -0
  551. ciris_engine/logic/processors/__init__.py +30 -0
  552. ciris_engine/logic/processors/core/__init__.py +1 -0
  553. ciris_engine/logic/processors/core/base_processor.py +280 -0
  554. ciris_engine/logic/processors/core/main_processor.py +1777 -0
  555. ciris_engine/logic/processors/core/step_decorators.py +1583 -0
  556. ciris_engine/logic/processors/core/thought_processor/__init__.py +20 -0
  557. ciris_engine/logic/processors/core/thought_processor/action_execution.py +49 -0
  558. ciris_engine/logic/processors/core/thought_processor/conscience_execution.py +382 -0
  559. ciris_engine/logic/processors/core/thought_processor/finalize_action.py +66 -0
  560. ciris_engine/logic/processors/core/thought_processor/gather_context.py +120 -0
  561. ciris_engine/logic/processors/core/thought_processor/main.py +920 -0
  562. ciris_engine/logic/processors/core/thought_processor/perform_aspdma.py +86 -0
  563. ciris_engine/logic/processors/core/thought_processor/perform_dmas.py +106 -0
  564. ciris_engine/logic/processors/core/thought_processor/recursive_processing.py +237 -0
  565. ciris_engine/logic/processors/core/thought_processor/round_complete.py +52 -0
  566. ciris_engine/logic/processors/core/thought_processor/start_round.py +64 -0
  567. ciris_engine/logic/processors/exceptions.py +59 -0
  568. ciris_engine/logic/processors/states/__init__.py +1 -0
  569. ciris_engine/logic/processors/states/dream_processor.py +1381 -0
  570. ciris_engine/logic/processors/states/play_processor.py +141 -0
  571. ciris_engine/logic/processors/states/shutdown_processor.py +623 -0
  572. ciris_engine/logic/processors/states/solitude_processor.py +305 -0
  573. ciris_engine/logic/processors/states/wakeup_processor.py +802 -0
  574. ciris_engine/logic/processors/states/work_processor.py +742 -0
  575. ciris_engine/logic/processors/support/__init__.py +1 -0
  576. ciris_engine/logic/processors/support/dma_orchestrator.py +336 -0
  577. ciris_engine/logic/processors/support/processing_queue.py +133 -0
  578. ciris_engine/logic/processors/support/shutdown_condition_evaluator.py +294 -0
  579. ciris_engine/logic/processors/support/state_manager.py +358 -0
  580. ciris_engine/logic/processors/support/task_manager.py +303 -0
  581. ciris_engine/logic/processors/support/thought_escalation.py +116 -0
  582. ciris_engine/logic/processors/support/thought_manager.py +328 -0
  583. ciris_engine/logic/processors/support/thought_manager_enhanced.py +105 -0
  584. ciris_engine/logic/registries/__init__.py +34 -0
  585. ciris_engine/logic/registries/base.py +653 -0
  586. ciris_engine/logic/registries/circuit_breaker.py +275 -0
  587. ciris_engine/logic/registries/typed_registries.py +184 -0
  588. ciris_engine/logic/runtime/__init__.py +7 -0
  589. ciris_engine/logic/runtime/adapter_loader.py +261 -0
  590. ciris_engine/logic/runtime/adapter_manager.py +1053 -0
  591. ciris_engine/logic/runtime/ciris_runtime.py +2342 -0
  592. ciris_engine/logic/runtime/ciris_runtime_helpers.py +923 -0
  593. ciris_engine/logic/runtime/component_builder.py +361 -0
  594. ciris_engine/logic/runtime/identity_manager.py +219 -0
  595. ciris_engine/logic/runtime/module_loader.py +207 -0
  596. ciris_engine/logic/runtime/prevent_sideeffects.py +30 -0
  597. ciris_engine/logic/runtime/runtime_interface.py +23 -0
  598. ciris_engine/logic/runtime/service_initializer.py +1623 -0
  599. ciris_engine/logic/secrets/__init__.py +30 -0
  600. ciris_engine/logic/secrets/encryption.py +175 -0
  601. ciris_engine/logic/secrets/filter.py +295 -0
  602. ciris_engine/logic/secrets/service.py +652 -0
  603. ciris_engine/logic/secrets/store.py +669 -0
  604. ciris_engine/logic/services/__init__.py +1 -0
  605. ciris_engine/logic/services/adaptation/__init__.py +3 -0
  606. ciris_engine/logic/services/base_graph_service.py +142 -0
  607. ciris_engine/logic/services/base_infrastructure_service.py +69 -0
  608. ciris_engine/logic/services/base_scheduled_service.py +136 -0
  609. ciris_engine/logic/services/base_service.py +247 -0
  610. ciris_engine/logic/services/governance/__init__.py +3 -0
  611. ciris_engine/logic/services/governance/adaptive_filter/__init__.py +14 -0
  612. ciris_engine/logic/services/governance/adaptive_filter/service.py +818 -0
  613. ciris_engine/logic/services/governance/consent/__init__.py +53 -0
  614. ciris_engine/logic/services/governance/consent/air.py +403 -0
  615. ciris_engine/logic/services/governance/consent/decay.py +324 -0
  616. ciris_engine/logic/services/governance/consent/dsar_automation.py +589 -0
  617. ciris_engine/logic/services/governance/consent/exceptions.py +106 -0
  618. ciris_engine/logic/services/governance/consent/metrics.py +270 -0
  619. ciris_engine/logic/services/governance/consent/partnership.py +533 -0
  620. ciris_engine/logic/services/governance/consent/service.py +1256 -0
  621. ciris_engine/logic/services/governance/dsar/__init__.py +29 -0
  622. ciris_engine/logic/services/governance/dsar/orchestrator.py +977 -0
  623. ciris_engine/logic/services/governance/dsar/schemas.py +141 -0
  624. ciris_engine/logic/services/governance/dsar/signature_service.py +283 -0
  625. ciris_engine/logic/services/governance/self_observation/__init__.py +20 -0
  626. ciris_engine/logic/services/governance/self_observation/service.py +1153 -0
  627. ciris_engine/logic/services/governance/visibility/__init__.py +17 -0
  628. ciris_engine/logic/services/governance/visibility/service.py +512 -0
  629. ciris_engine/logic/services/governance/wise_authority/__init__.py +15 -0
  630. ciris_engine/logic/services/governance/wise_authority/service.py +827 -0
  631. ciris_engine/logic/services/graph/__init__.py +5 -0
  632. ciris_engine/logic/services/graph/audit_service/__init__.py +5 -0
  633. ciris_engine/logic/services/graph/audit_service/service.py +1675 -0
  634. ciris_engine/logic/services/graph/base.py +208 -0
  635. ciris_engine/logic/services/graph/config_service/__init__.py +5 -0
  636. ciris_engine/logic/services/graph/config_service/service.py +372 -0
  637. ciris_engine/logic/services/graph/incident_service/__init__.py +5 -0
  638. ciris_engine/logic/services/graph/incident_service/service.py +803 -0
  639. ciris_engine/logic/services/graph/memory_service.py +1120 -0
  640. ciris_engine/logic/services/graph/telemetry_service/__init__.py +5 -0
  641. ciris_engine/logic/services/graph/telemetry_service/exceptions.py +104 -0
  642. ciris_engine/logic/services/graph/telemetry_service/helpers.py +1337 -0
  643. ciris_engine/logic/services/graph/telemetry_service/service.py +2429 -0
  644. ciris_engine/logic/services/graph/tsdb_consolidation/__init__.py +17 -0
  645. ciris_engine/logic/services/graph/tsdb_consolidation/aggregation_helpers.py +355 -0
  646. ciris_engine/logic/services/graph/tsdb_consolidation/cleanup_helpers.py +438 -0
  647. ciris_engine/logic/services/graph/tsdb_consolidation/compressor.py +260 -0
  648. ciris_engine/logic/services/graph/tsdb_consolidation/consolidators/__init__.py +27 -0
  649. ciris_engine/logic/services/graph/tsdb_consolidation/consolidators/audit.py +326 -0
  650. ciris_engine/logic/services/graph/tsdb_consolidation/consolidators/conversation.py +291 -0
  651. ciris_engine/logic/services/graph/tsdb_consolidation/consolidators/memory.py +197 -0
  652. ciris_engine/logic/services/graph/tsdb_consolidation/consolidators/metrics.py +251 -0
  653. ciris_engine/logic/services/graph/tsdb_consolidation/consolidators/task.py +257 -0
  654. ciris_engine/logic/services/graph/tsdb_consolidation/consolidators/trace.py +363 -0
  655. ciris_engine/logic/services/graph/tsdb_consolidation/data_converter.py +545 -0
  656. ciris_engine/logic/services/graph/tsdb_consolidation/date_calculation_helpers.py +193 -0
  657. ciris_engine/logic/services/graph/tsdb_consolidation/db_query_helpers.py +296 -0
  658. ciris_engine/logic/services/graph/tsdb_consolidation/edge_helpers.py +92 -0
  659. ciris_engine/logic/services/graph/tsdb_consolidation/edge_manager.py +896 -0
  660. ciris_engine/logic/services/graph/tsdb_consolidation/extensive_helpers.py +322 -0
  661. ciris_engine/logic/services/graph/tsdb_consolidation/period_manager.py +152 -0
  662. ciris_engine/logic/services/graph/tsdb_consolidation/profound_helpers.py +277 -0
  663. ciris_engine/logic/services/graph/tsdb_consolidation/query_manager.py +812 -0
  664. ciris_engine/logic/services/graph/tsdb_consolidation/service.py +1692 -0
  665. ciris_engine/logic/services/graph/tsdb_consolidation/sql_builders.py +363 -0
  666. ciris_engine/logic/services/infrastructure/__init__.py +1 -0
  667. ciris_engine/logic/services/infrastructure/authentication/__init__.py +5 -0
  668. ciris_engine/logic/services/infrastructure/authentication/service.py +1634 -0
  669. ciris_engine/logic/services/infrastructure/database_maintenance/__init__.py +15 -0
  670. ciris_engine/logic/services/infrastructure/database_maintenance/service.py +764 -0
  671. ciris_engine/logic/services/infrastructure/resource_monitor/__init__.py +7 -0
  672. ciris_engine/logic/services/infrastructure/resource_monitor/ciris_billing_provider.py +755 -0
  673. ciris_engine/logic/services/infrastructure/resource_monitor/service.py +409 -0
  674. ciris_engine/logic/services/infrastructure/resource_monitor/simple_credit_provider.py +129 -0
  675. ciris_engine/logic/services/lifecycle/__init__.py +3 -0
  676. ciris_engine/logic/services/lifecycle/initialization/__init__.py +10 -0
  677. ciris_engine/logic/services/lifecycle/initialization/service.py +312 -0
  678. ciris_engine/logic/services/lifecycle/scheduler/__init__.py +5 -0
  679. ciris_engine/logic/services/lifecycle/scheduler/service.py +607 -0
  680. ciris_engine/logic/services/lifecycle/shutdown/__init__.py +9 -0
  681. ciris_engine/logic/services/lifecycle/shutdown/service.py +378 -0
  682. ciris_engine/logic/services/lifecycle/time/__init__.py +15 -0
  683. ciris_engine/logic/services/lifecycle/time/service.py +259 -0
  684. ciris_engine/logic/services/memory_service/__init__.py +8 -0
  685. ciris_engine/logic/services/mixins/__init__.py +13 -0
  686. ciris_engine/logic/services/mixins/example_usage.py +200 -0
  687. ciris_engine/logic/services/mixins/request_metrics.py +179 -0
  688. ciris_engine/logic/services/runtime/__init__.py +3 -0
  689. ciris_engine/logic/services/runtime/adapter_configuration/__init__.py +16 -0
  690. ciris_engine/logic/services/runtime/adapter_configuration/service.py +674 -0
  691. ciris_engine/logic/services/runtime/adapter_configuration/session.py +67 -0
  692. ciris_engine/logic/services/runtime/control_service/__init__.py +5 -0
  693. ciris_engine/logic/services/runtime/control_service/service.py +2269 -0
  694. ciris_engine/logic/services/runtime/llm_service/__init__.py +14 -0
  695. ciris_engine/logic/services/runtime/llm_service/pricing_calculator.py +279 -0
  696. ciris_engine/logic/services/runtime/llm_service/service.py +930 -0
  697. ciris_engine/logic/services/tools/__init__.py +5 -0
  698. ciris_engine/logic/services/tools/core_tool_service/__init__.py +8 -0
  699. ciris_engine/logic/services/tools/core_tool_service/service.py +852 -0
  700. ciris_engine/logic/setup/__init__.py +1 -0
  701. ciris_engine/logic/setup/first_run.py +250 -0
  702. ciris_engine/logic/setup/wizard.py +327 -0
  703. ciris_engine/logic/telemetry/__init__.py +46 -0
  704. ciris_engine/logic/telemetry/core.py +239 -0
  705. ciris_engine/logic/telemetry/hot_cold_config.py +133 -0
  706. ciris_engine/logic/telemetry/log_collector.py +190 -0
  707. ciris_engine/logic/telemetry/resource_monitor.py +7 -0
  708. ciris_engine/logic/telemetry/security.py +79 -0
  709. ciris_engine/logic/utils/__init__.py +18 -0
  710. ciris_engine/logic/utils/channel_utils.py +75 -0
  711. ciris_engine/logic/utils/consent/__init__.py +1 -0
  712. ciris_engine/logic/utils/consent/partnership_utils.py +172 -0
  713. ciris_engine/logic/utils/constants.py +92 -0
  714. ciris_engine/logic/utils/context_utils.py +145 -0
  715. ciris_engine/logic/utils/directory_setup.py +533 -0
  716. ciris_engine/logic/utils/graphql_context_provider.py +152 -0
  717. ciris_engine/logic/utils/identity_resolution.py +843 -0
  718. ciris_engine/logic/utils/incident_capture_handler.py +303 -0
  719. ciris_engine/logic/utils/initialization_manager.py +74 -0
  720. ciris_engine/logic/utils/jsondict_helpers.py +290 -0
  721. ciris_engine/logic/utils/log_sanitizer.py +97 -0
  722. ciris_engine/logic/utils/logging_config.py +151 -0
  723. ciris_engine/logic/utils/observability_decorators.py +544 -0
  724. ciris_engine/logic/utils/occurrence_utils.py +155 -0
  725. ciris_engine/logic/utils/path_resolution.py +281 -0
  726. ciris_engine/logic/utils/platform_detection.py +286 -0
  727. ciris_engine/logic/utils/privacy.py +266 -0
  728. ciris_engine/logic/utils/profile_loader.py +124 -0
  729. ciris_engine/logic/utils/profile_manager.py +16 -0
  730. ciris_engine/logic/utils/runtime_utils.py +69 -0
  731. ciris_engine/logic/utils/shutdown_manager.py +107 -0
  732. ciris_engine/logic/utils/task_formatters.py +60 -0
  733. ciris_engine/logic/utils/task_thought_factory.py +404 -0
  734. ciris_engine/logic/utils/thought_utils.py +54 -0
  735. ciris_engine/logic/utils/user_utils.py +70 -0
  736. ciris_engine/protocols/__init__.py +0 -0
  737. ciris_engine/protocols/adapters/__init__.py +35 -0
  738. ciris_engine/protocols/adapters/base.py +149 -0
  739. ciris_engine/protocols/adapters/configurable.py +265 -0
  740. ciris_engine/protocols/adapters/message.py +90 -0
  741. ciris_engine/protocols/audit/__init__.py +1 -0
  742. ciris_engine/protocols/buses/__init__.py +1 -0
  743. ciris_engine/protocols/config/__init__.py +1 -0
  744. ciris_engine/protocols/conscience/__init__.py +1 -0
  745. ciris_engine/protocols/consent.py +88 -0
  746. ciris_engine/protocols/context/__init__.py +1 -0
  747. ciris_engine/protocols/data/__init__.py +1 -0
  748. ciris_engine/protocols/dma/__init__.py +1 -0
  749. ciris_engine/protocols/dma/base.py +107 -0
  750. ciris_engine/protocols/faculties.py +34 -0
  751. ciris_engine/protocols/formatters/__init__.py +1 -0
  752. ciris_engine/protocols/handlers/__init__.py +1 -0
  753. ciris_engine/protocols/infrastructure/__init__.py +25 -0
  754. ciris_engine/protocols/infrastructure/base.py +377 -0
  755. ciris_engine/protocols/persistence/__init__.py +1 -0
  756. ciris_engine/protocols/pipeline_control.py +609 -0
  757. ciris_engine/protocols/processors/__init__.py +19 -0
  758. ciris_engine/protocols/processors/agent.py +299 -0
  759. ciris_engine/protocols/processors/base.py +130 -0
  760. ciris_engine/protocols/processors/orchestration.py +62 -0
  761. ciris_engine/protocols/registries/__init__.py +1 -0
  762. ciris_engine/protocols/runtime/__init__.py +1 -0
  763. ciris_engine/protocols/runtime/base.py +163 -0
  764. ciris_engine/protocols/secrets/__init__.py +1 -0
  765. ciris_engine/protocols/services/__init__.py +80 -0
  766. ciris_engine/protocols/services/adaptation/__init__.py +7 -0
  767. ciris_engine/protocols/services/adaptation/self_observation.py +265 -0
  768. ciris_engine/protocols/services/governance/__init__.py +20 -0
  769. ciris_engine/protocols/services/governance/communication.py +58 -0
  770. ciris_engine/protocols/services/governance/filter.py +56 -0
  771. ciris_engine/protocols/services/governance/visibility.py +32 -0
  772. ciris_engine/protocols/services/governance/wa_auth.py +192 -0
  773. ciris_engine/protocols/services/governance/wise_authority.py +75 -0
  774. ciris_engine/protocols/services/graph/__init__.py +19 -0
  775. ciris_engine/protocols/services/graph/audit.py +92 -0
  776. ciris_engine/protocols/services/graph/config.py +54 -0
  777. ciris_engine/protocols/services/graph/incident_management.py +103 -0
  778. ciris_engine/protocols/services/graph/memory.py +110 -0
  779. ciris_engine/protocols/services/graph/telemetry.py +51 -0
  780. ciris_engine/protocols/services/graph/tsdb_consolidation.py +87 -0
  781. ciris_engine/protocols/services/infrastructure/__init__.py +11 -0
  782. ciris_engine/protocols/services/infrastructure/authentication.py +159 -0
  783. ciris_engine/protocols/services/infrastructure/credit_gate.py +46 -0
  784. ciris_engine/protocols/services/infrastructure/database_maintenance.py +25 -0
  785. ciris_engine/protocols/services/infrastructure/resource_monitor.py +83 -0
  786. ciris_engine/protocols/services/lifecycle/__init__.py +13 -0
  787. ciris_engine/protocols/services/lifecycle/initialization.py +41 -0
  788. ciris_engine/protocols/services/lifecycle/scheduler.py +42 -0
  789. ciris_engine/protocols/services/lifecycle/shutdown.py +50 -0
  790. ciris_engine/protocols/services/lifecycle/time.py +31 -0
  791. ciris_engine/protocols/services/runtime/__init__.py +13 -0
  792. ciris_engine/protocols/services/runtime/llm.py +50 -0
  793. ciris_engine/protocols/services/runtime/runtime_control.py +193 -0
  794. ciris_engine/protocols/services/runtime/secrets.py +100 -0
  795. ciris_engine/protocols/services/runtime/tool.py +123 -0
  796. ciris_engine/protocols/telemetry/__init__.py +1 -0
  797. ciris_engine/protocols/utils/__init__.py +1 -0
  798. ciris_engine/schemas/__init__.py +112 -0
  799. ciris_engine/schemas/actions/__init__.py +37 -0
  800. ciris_engine/schemas/actions/parameters.py +137 -0
  801. ciris_engine/schemas/adapters/__init__.py +13 -0
  802. ciris_engine/schemas/adapters/cirisnode.py +135 -0
  803. ciris_engine/schemas/adapters/cli.py +97 -0
  804. ciris_engine/schemas/adapters/cli_tools.py +98 -0
  805. ciris_engine/schemas/adapters/discord.py +125 -0
  806. ciris_engine/schemas/adapters/graphql_core.py +144 -0
  807. ciris_engine/schemas/adapters/registration.py +47 -0
  808. ciris_engine/schemas/adapters/runtime_context.py +48 -0
  809. ciris_engine/schemas/adapters/tool_execution.py +45 -0
  810. ciris_engine/schemas/adapters/tools.py +96 -0
  811. ciris_engine/schemas/api/__init__.py +1 -0
  812. ciris_engine/schemas/api/agent.py +50 -0
  813. ciris_engine/schemas/api/audit.py +38 -0
  814. ciris_engine/schemas/api/auth.py +351 -0
  815. ciris_engine/schemas/api/config_security.py +242 -0
  816. ciris_engine/schemas/api/emergency.py +111 -0
  817. ciris_engine/schemas/api/responses.py +72 -0
  818. ciris_engine/schemas/api/runtime.py +26 -0
  819. ciris_engine/schemas/api/telemetry.py +109 -0
  820. ciris_engine/schemas/api/wa.py +90 -0
  821. ciris_engine/schemas/audit/__init__.py +13 -0
  822. ciris_engine/schemas/audit/core.py +139 -0
  823. ciris_engine/schemas/audit/hash_chain.py +58 -0
  824. ciris_engine/schemas/audit/verification.py +131 -0
  825. ciris_engine/schemas/buses/__init__.py +1 -0
  826. ciris_engine/schemas/config/__init__.py +41 -0
  827. ciris_engine/schemas/config/agent.py +279 -0
  828. ciris_engine/schemas/config/cognitive_state_behaviors.py +194 -0
  829. ciris_engine/schemas/config/default_dsar_sops.py +178 -0
  830. ciris_engine/schemas/config/essential.py +195 -0
  831. ciris_engine/schemas/config/tickets.py +86 -0
  832. ciris_engine/schemas/conscience/__init__.py +25 -0
  833. ciris_engine/schemas/conscience/context.py +34 -0
  834. ciris_engine/schemas/conscience/core.py +145 -0
  835. ciris_engine/schemas/conscience/results.py +24 -0
  836. ciris_engine/schemas/consent/__init__.py +5 -0
  837. ciris_engine/schemas/consent/core.py +404 -0
  838. ciris_engine/schemas/context/__init__.py +1 -0
  839. ciris_engine/schemas/covenant.py +382 -0
  840. ciris_engine/schemas/data/__init__.py +1 -0
  841. ciris_engine/schemas/dma/__init__.py +16 -0
  842. ciris_engine/schemas/dma/core.py +199 -0
  843. ciris_engine/schemas/dma/faculty.py +192 -0
  844. ciris_engine/schemas/dma/prompts.py +172 -0
  845. ciris_engine/schemas/dma/results.py +103 -0
  846. ciris_engine/schemas/formatters/__init__.py +1 -0
  847. ciris_engine/schemas/handlers/__init__.py +10 -0
  848. ciris_engine/schemas/handlers/context.py +119 -0
  849. ciris_engine/schemas/handlers/contexts.py +100 -0
  850. ciris_engine/schemas/handlers/core.py +167 -0
  851. ciris_engine/schemas/handlers/memory_schemas.py +67 -0
  852. ciris_engine/schemas/handlers/schemas.py +95 -0
  853. ciris_engine/schemas/identity.py +149 -0
  854. ciris_engine/schemas/infrastructure/__init__.py +1 -0
  855. ciris_engine/schemas/infrastructure/base.py +256 -0
  856. ciris_engine/schemas/infrastructure/behavioral_patterns.py +129 -0
  857. ciris_engine/schemas/infrastructure/feedback_loop.py +57 -0
  858. ciris_engine/schemas/infrastructure/identity_variance.py +141 -0
  859. ciris_engine/schemas/infrastructure/oauth.py +175 -0
  860. ciris_engine/schemas/infrastructure/wa_cli_wizard.py +54 -0
  861. ciris_engine/schemas/persistence/__init__.py +34 -0
  862. ciris_engine/schemas/persistence/core.py +140 -0
  863. ciris_engine/schemas/persistence/correlations.py +73 -0
  864. ciris_engine/schemas/persistence/postgres/__init__.py +1 -0
  865. ciris_engine/schemas/persistence/postgres/tables.py +280 -0
  866. ciris_engine/schemas/persistence/sqlite/__init__.py +1 -0
  867. ciris_engine/schemas/persistence/sqlite/tables.py +281 -0
  868. ciris_engine/schemas/platform.py +149 -0
  869. ciris_engine/schemas/processors/__init__.py +26 -0
  870. ciris_engine/schemas/processors/base.py +130 -0
  871. ciris_engine/schemas/processors/cognitive.py +77 -0
  872. ciris_engine/schemas/processors/context.py +35 -0
  873. ciris_engine/schemas/processors/core.py +152 -0
  874. ciris_engine/schemas/processors/dma.py +105 -0
  875. ciris_engine/schemas/processors/error.py +122 -0
  876. ciris_engine/schemas/processors/main.py +109 -0
  877. ciris_engine/schemas/processors/phase_results.py +21 -0
  878. ciris_engine/schemas/processors/results.py +99 -0
  879. ciris_engine/schemas/processors/solitude.py +79 -0
  880. ciris_engine/schemas/processors/state.py +202 -0
  881. ciris_engine/schemas/processors/state_example.py +177 -0
  882. ciris_engine/schemas/processors/states.py +21 -0
  883. ciris_engine/schemas/processors/status.py +34 -0
  884. ciris_engine/schemas/registries/__init__.py +1 -0
  885. ciris_engine/schemas/registries/base.py +66 -0
  886. ciris_engine/schemas/resources/__init__.py +15 -0
  887. ciris_engine/schemas/resources/crisis.py +315 -0
  888. ciris_engine/schemas/runtime/__init__.py +42 -0
  889. ciris_engine/schemas/runtime/adapter_management.py +186 -0
  890. ciris_engine/schemas/runtime/api.py +58 -0
  891. ciris_engine/schemas/runtime/audit.py +50 -0
  892. ciris_engine/schemas/runtime/bootstrap.py +33 -0
  893. ciris_engine/schemas/runtime/contexts.py +61 -0
  894. ciris_engine/schemas/runtime/core.py +161 -0
  895. ciris_engine/schemas/runtime/enums.py +167 -0
  896. ciris_engine/schemas/runtime/extended.py +232 -0
  897. ciris_engine/schemas/runtime/manifest.py +311 -0
  898. ciris_engine/schemas/runtime/memory.py +60 -0
  899. ciris_engine/schemas/runtime/messages.py +108 -0
  900. ciris_engine/schemas/runtime/models.py +156 -0
  901. ciris_engine/schemas/runtime/processing_context.py +43 -0
  902. ciris_engine/schemas/runtime/protocols_core.py +96 -0
  903. ciris_engine/schemas/runtime/resources.py +33 -0
  904. ciris_engine/schemas/runtime/system_context.py +417 -0
  905. ciris_engine/schemas/secrets/__init__.py +1 -0
  906. ciris_engine/schemas/secrets/core.py +267 -0
  907. ciris_engine/schemas/secrets/service.py +95 -0
  908. ciris_engine/schemas/services/__init__.py +33 -0
  909. ciris_engine/schemas/services/audit_summary_node.py +172 -0
  910. ciris_engine/schemas/services/authority/__init__.py +39 -0
  911. ciris_engine/schemas/services/authority/jwt.py +158 -0
  912. ciris_engine/schemas/services/authority/wa_updates.py +138 -0
  913. ciris_engine/schemas/services/authority/wise_authority.py +163 -0
  914. ciris_engine/schemas/services/authority_core.py +370 -0
  915. ciris_engine/schemas/services/capabilities.py +72 -0
  916. ciris_engine/schemas/services/community_core.py +95 -0
  917. ciris_engine/schemas/services/context.py +111 -0
  918. ciris_engine/schemas/services/conversation_summary_node.py +189 -0
  919. ciris_engine/schemas/services/core/__init__.py +153 -0
  920. ciris_engine/schemas/services/core/runtime.py +262 -0
  921. ciris_engine/schemas/services/core/runtime_config.py +117 -0
  922. ciris_engine/schemas/services/core/secrets.py +65 -0
  923. ciris_engine/schemas/services/correlation_node.py +179 -0
  924. ciris_engine/schemas/services/credit_gate.py +92 -0
  925. ciris_engine/schemas/services/discord_nodes.py +299 -0
  926. ciris_engine/schemas/services/feedback_core.py +131 -0
  927. ciris_engine/schemas/services/filters_core.py +270 -0
  928. ciris_engine/schemas/services/governance.py +26 -0
  929. ciris_engine/schemas/services/graph/__init__.py +26 -0
  930. ciris_engine/schemas/services/graph/attributes.py +254 -0
  931. ciris_engine/schemas/services/graph/audit.py +98 -0
  932. ciris_engine/schemas/services/graph/consolidation.py +338 -0
  933. ciris_engine/schemas/services/graph/edge_types.py +43 -0
  934. ciris_engine/schemas/services/graph/edges.py +88 -0
  935. ciris_engine/schemas/services/graph/incident.py +312 -0
  936. ciris_engine/schemas/services/graph/memory.py +84 -0
  937. ciris_engine/schemas/services/graph/node_data.py +174 -0
  938. ciris_engine/schemas/services/graph/query_results.py +82 -0
  939. ciris_engine/schemas/services/graph/telemetry.py +250 -0
  940. ciris_engine/schemas/services/graph/tsdb_consolidation.py +27 -0
  941. ciris_engine/schemas/services/graph/tsdb_models.py +107 -0
  942. ciris_engine/schemas/services/graph_core.py +196 -0
  943. ciris_engine/schemas/services/graph_typed_nodes.py +194 -0
  944. ciris_engine/schemas/services/infrastructure/__init__.py +1 -0
  945. ciris_engine/schemas/services/infrastructure/resource_monitor.py +20 -0
  946. ciris_engine/schemas/services/lifecycle/__init__.py +9 -0
  947. ciris_engine/schemas/services/lifecycle/initialization.py +33 -0
  948. ciris_engine/schemas/services/lifecycle/time.py +50 -0
  949. ciris_engine/schemas/services/llm.py +187 -0
  950. ciris_engine/schemas/services/metadata.py +43 -0
  951. ciris_engine/schemas/services/nodes.py +704 -0
  952. ciris_engine/schemas/services/operations.py +126 -0
  953. ciris_engine/schemas/services/requests.py +128 -0
  954. ciris_engine/schemas/services/resources_core.py +182 -0
  955. ciris_engine/schemas/services/runtime_control.py +1010 -0
  956. ciris_engine/schemas/services/shutdown.py +88 -0
  957. ciris_engine/schemas/services/special/__init__.py +0 -0
  958. ciris_engine/schemas/services/special/self_observation.py +396 -0
  959. ciris_engine/schemas/services/trace_summary_node.py +199 -0
  960. ciris_engine/schemas/services/visibility.py +98 -0
  961. ciris_engine/schemas/streaming/__init__.py +10 -0
  962. ciris_engine/schemas/streaming/reasoning_stream.py +95 -0
  963. ciris_engine/schemas/telemetry/__init__.py +0 -0
  964. ciris_engine/schemas/telemetry/collector.py +67 -0
  965. ciris_engine/schemas/telemetry/core.py +252 -0
  966. ciris_engine/schemas/telemetry/unified.py +59 -0
  967. ciris_engine/schemas/tools.py +72 -0
  968. ciris_engine/schemas/types.py +47 -0
  969. ciris_engine/schemas/utils/__init__.py +1 -0
  970. ciris_engine/schemas/utils/config_validator.py +54 -0
  971. ciris_engine/utils/__init__.py +1 -0
  972. ciris_engine/utils/serialization.py +35 -0
  973. ciris_sdk/__init__.py +124 -0
  974. ciris_sdk/auth_store.py +261 -0
  975. ciris_sdk/client.py +261 -0
  976. ciris_sdk/exceptions.py +73 -0
  977. ciris_sdk/model_types.py +258 -0
  978. ciris_sdk/models.py +354 -0
  979. ciris_sdk/pagination.py +214 -0
  980. ciris_sdk/rate_limiter.py +188 -0
  981. ciris_sdk/setup.py +17 -0
  982. ciris_sdk/telemetry_models.py +257 -0
  983. ciris_sdk/telemetry_responses.py +199 -0
  984. ciris_sdk/transport.py +177 -0
  985. ciris_sdk/websocket.py +400 -0
  986. main.py +766 -0
@@ -0,0 +1,1417 @@
1
+ """Authentication service for API v2.0.
2
+
3
+ Manages API keys, OAuth users, and authentication state.
4
+ """
5
+
6
+ import base64
7
+ import hashlib
8
+ import logging
9
+ import secrets
10
+ from dataclasses import dataclass, field
11
+ from datetime import datetime, timedelta, timezone
12
+ from typing import Any, Dict, List, Optional
13
+
14
+ import aiofiles
15
+ import bcrypt
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+ from ciris_engine.protocols.services.infrastructure.authentication import AuthenticationServiceProtocol
20
+ from ciris_engine.schemas.api.auth import UserRole
21
+ from ciris_engine.schemas.runtime.api import APIRole
22
+ from ciris_engine.schemas.services.authority.wise_authority import WAUpdate
23
+ from ciris_engine.schemas.services.authority_core import OAuthIdentityLink, WACertificate, WARole
24
+
25
+ # Permission constants to avoid duplication
26
+ PERMISSION_SYSTEM_READ = "system.read"
27
+ PERMISSION_SYSTEM_WRITE = "system.write"
28
+ PERMISSION_MEMORY_READ = "memory.read"
29
+ PERMISSION_MEMORY_WRITE = "memory.write"
30
+ PERMISSION_TELEMETRY_READ = "telemetry.read"
31
+ PERMISSION_CONFIG_READ = "config.read"
32
+ PERMISSION_CONFIG_WRITE = "config.write"
33
+ PERMISSION_AUDIT_READ = "audit.read"
34
+ PERMISSION_AUDIT_WRITE = "audit.write"
35
+ PERMISSION_USERS_READ = "users.read"
36
+ PERMISSION_USERS_WRITE = "users.write"
37
+ PERMISSION_USERS_DELETE = "users.delete"
38
+ PERMISSION_WA_READ = "wa.read"
39
+ PERMISSION_WA_WRITE = "wa.write"
40
+ PERMISSION_WA_MINT = "wa.mint"
41
+ PERMISSION_EMERGENCY_SHUTDOWN = "emergency.shutdown"
42
+ PERMISSION_MANAGE_USER_PERMISSIONS = "manage_user_permissions"
43
+
44
+
45
+ @dataclass
46
+ class StoredAPIKey:
47
+ """Internal representation of an API key."""
48
+
49
+ key_id: str # Unique ID for the key
50
+ key_hash: str
51
+ key_value: str # Masked version for display
52
+ user_id: str
53
+ role: UserRole
54
+ expires_at: Optional[datetime]
55
+ description: Optional[str]
56
+ created_at: datetime
57
+ created_by: str
58
+ last_used: Optional[datetime]
59
+ is_active: bool
60
+
61
+
62
+ @dataclass
63
+ class OAuthUser:
64
+ """OAuth user information."""
65
+
66
+ user_id: str # Format: provider:external_id
67
+ provider: str
68
+ external_id: str
69
+ email: Optional[str]
70
+ name: Optional[str]
71
+ role: UserRole
72
+ created_at: datetime
73
+ last_login: datetime
74
+ marketing_opt_in: bool = False # User consent for marketing communications
75
+
76
+
77
+ @dataclass
78
+ class User:
79
+ """Unified user representation combining auth methods and WA status."""
80
+
81
+ wa_id: str # Primary ID (from WA cert)
82
+ name: str
83
+ auth_type: str # "password", "oauth", "api_key"
84
+ api_role: APIRole
85
+ wa_role: Optional[WARole] = None
86
+ oauth_provider: Optional[str] = None
87
+ oauth_email: Optional[str] = None
88
+ oauth_external_id: Optional[str] = None
89
+ created_at: Optional[datetime] = None
90
+ last_login: Optional[datetime] = None
91
+ is_active: bool = True
92
+ wa_parent_id: Optional[str] = None
93
+ wa_auto_minted: bool = False
94
+ password_hash: Optional[str] = None
95
+ custom_permissions: Optional[List[str]] = None # Additional permissions beyond role defaults
96
+ # OAuth profile fields for permission request system
97
+ oauth_name: Optional[str] = None # Full name from OAuth provider
98
+ oauth_picture: Optional[str] = None # Profile picture URL from OAuth provider
99
+ permission_requested_at: Optional[datetime] = None # Timestamp when user requested permissions
100
+ oauth_links: List[OAuthIdentityLink] = field(default_factory=list)
101
+ marketing_opt_in: bool = False # User consent for marketing communications
102
+
103
+
104
+ class APIAuthService:
105
+ """Simple in-memory authentication service with database persistence."""
106
+
107
+ # Class-level instance counter to track re-initialization
108
+ _instance_counter = 0
109
+
110
+ def __init__(self, auth_service: Optional[AuthenticationServiceProtocol] = None) -> None:
111
+ # Track instance creation for debugging
112
+ APIAuthService._instance_counter += 1
113
+ self._instance_id = APIAuthService._instance_counter
114
+ logger.debug(
115
+ f"[AUTH SERVICE DEBUG] APIAuthService.__init__ called - INSTANCE #{self._instance_id} created (id={id(self)})"
116
+ )
117
+
118
+ # In-memory caches for performance
119
+ self._api_keys: Dict[str, StoredAPIKey] = {}
120
+ self._oauth_users: Dict[str, OAuthUser] = {}
121
+ self._users: Dict[str, User] = {}
122
+
123
+ logger.debug(
124
+ f"[AUTH SERVICE DEBUG] Instance #{self._instance_id} - _api_keys initialized as EMPTY dict (id={id(self._api_keys)})"
125
+ )
126
+
127
+ # Store reference to the actual authentication service
128
+ self._auth_service = auth_service
129
+
130
+ # Flag to track if we've loaded users from DB
131
+ self._users_loaded = False
132
+
133
+ # Don't load from DB in __init__ - this causes asyncio.run() errors
134
+ # Instead, we'll load lazily on first access
135
+ if not self._auth_service:
136
+ # Fallback: Initialize with system admin user if no auth service
137
+ now = datetime.now(timezone.utc)
138
+ admin_user = User(
139
+ wa_id="wa-system-admin",
140
+ name="admin",
141
+ auth_type="password",
142
+ api_role=APIRole.SYSTEM_ADMIN,
143
+ wa_role=None, # System admin is not a WA by default
144
+ created_at=now,
145
+ is_active=True,
146
+ password_hash=self._hash_password("ciris_admin_password"),
147
+ )
148
+ self._users[admin_user.wa_id] = admin_user
149
+ self._users_loaded = True
150
+
151
+ async def _ensure_users_loaded(self) -> None:
152
+ """Ensure users are loaded from database (lazy loading)."""
153
+ if self._users_loaded:
154
+ return
155
+
156
+ await self._load_users_from_db()
157
+ self._users_loaded = True
158
+
159
+ async def reload_users_from_db(self) -> None:
160
+ """Force reload users from database, invalidating cache.
161
+
162
+ Use this after external changes to the user database (e.g., setup wizard).
163
+ """
164
+ self._users_loaded = False
165
+ await self._ensure_users_loaded()
166
+
167
+ def _update_existing_oauth_user(self, oauth_user_id: str, wa: "WACertificate") -> None:
168
+ """Update existing OAuth user record with WA info."""
169
+ existing_user = self._users[oauth_user_id]
170
+ existing_user.wa_role = wa.role
171
+ existing_user.wa_id = wa.wa_id
172
+ existing_user.wa_parent_id = wa.parent_wa_id
173
+ existing_user.wa_auto_minted = wa.auto_minted
174
+ existing_user.api_role = self._wa_role_to_api_role(wa.role)
175
+
176
+ def _create_user_from_wa(self, wa: "WACertificate") -> User:
177
+ """Convert WA certificate to User."""
178
+ # Extract email from oauth_links if available
179
+ oauth_email = None
180
+ if wa.oauth_links:
181
+ logger.debug(f"[AUTH DEBUG] Found {len(wa.oauth_links)} OAuth links for {wa.wa_id}")
182
+ for i, link in enumerate(wa.oauth_links):
183
+ logger.debug(
184
+ f"[AUTH DEBUG] Link {i}: provider={link.provider}, external_id={link.external_id}, metadata={link.metadata}"
185
+ )
186
+ # Check if link has email in metadata or as direct attribute
187
+ if hasattr(link, "email") and link.email:
188
+ oauth_email = link.email
189
+ masked = oauth_email[:3] + "***" if oauth_email else "None" # NOSONAR - masked email for debug
190
+ logger.debug(f"[AUTH DEBUG] Extracted email from link.email: {masked}")
191
+ break
192
+ elif hasattr(link, "metadata") and isinstance(link.metadata, dict):
193
+ if "email" in link.metadata:
194
+ oauth_email = link.metadata["email"]
195
+ masked = oauth_email[:3] + "***" if oauth_email else "None" # NOSONAR - masked email for debug
196
+ logger.debug(f"[AUTH DEBUG] Extracted email from link.metadata['email']: {masked}")
197
+ break
198
+ else:
199
+ logger.debug(f"[AUTH DEBUG] No OAuth links found for {wa.wa_id}")
200
+
201
+ return User(
202
+ wa_id=wa.wa_id,
203
+ name=wa.name,
204
+ auth_type="password" if wa.password_hash else "certificate",
205
+ api_role=self._wa_role_to_api_role(wa.role),
206
+ wa_role=wa.role,
207
+ created_at=wa.created_at,
208
+ last_login=wa.last_auth,
209
+ is_active=True, # Assume active if in database
210
+ wa_parent_id=wa.parent_wa_id,
211
+ wa_auto_minted=wa.auto_minted,
212
+ password_hash=wa.password_hash,
213
+ oauth_provider=wa.oauth_provider,
214
+ oauth_email=oauth_email, # Extract from oauth_links
215
+ oauth_external_id=wa.oauth_external_id,
216
+ custom_permissions=wa.custom_permissions if hasattr(wa, "custom_permissions") else None,
217
+ oauth_links=list(wa.oauth_links),
218
+ )
219
+
220
+ async def _process_wa_record(self, wa: "WACertificate") -> None:
221
+ """Process a single WA record and add/update user."""
222
+ logger.debug(f"[AUTH DEBUG] _process_wa_record: wa_id={wa.wa_id}, name={wa.name}")
223
+
224
+ # Remove stale cache entries for this WA
225
+ to_remove = [key for key, value in self._users.items() if getattr(value, "wa_id", None) == wa.wa_id]
226
+ if to_remove:
227
+ logger.debug(f"[AUTH DEBUG] Removing {len(to_remove)} stale entries for {wa.wa_id}")
228
+ for key in to_remove:
229
+ self._users.pop(key, None)
230
+
231
+ user = self._create_user_from_wa(wa)
232
+ logger.debug(
233
+ f"[AUTH DEBUG] Created User: name={user.name}, auth_type={user.auth_type}, has_password={user.password_hash is not None}"
234
+ )
235
+
236
+ self._users[wa.wa_id] = user
237
+ logger.debug(f"[AUTH DEBUG] Stored user under key: '{wa.wa_id}'")
238
+
239
+ if wa.oauth_provider and wa.oauth_external_id:
240
+ primary_key = f"{wa.oauth_provider}:{wa.oauth_external_id}"
241
+ self._users[primary_key] = user
242
+ logger.debug(f"[AUTH DEBUG] Stored user under OAuth key: '{primary_key}'")
243
+ # Clear from _oauth_users cache - DB record is authoritative
244
+ if primary_key in self._oauth_users:
245
+ logger.debug(f"[AUTH DEBUG] Clearing stale _oauth_users entry: '{primary_key}'")
246
+ del self._oauth_users[primary_key]
247
+
248
+ for link in wa.oauth_links:
249
+ link_key = f"{link.provider}:{link.external_id}"
250
+ self._users[link_key] = user
251
+ logger.debug(f"[AUTH DEBUG] Stored user under link key: '{link_key}'")
252
+
253
+ async def _load_users_from_db(self) -> None:
254
+ """Load existing users from the database."""
255
+ logger.info("=" * 70)
256
+ logger.info("CIRIS_USER_CREATE: _load_users_from_db() called")
257
+ logger.info("=" * 70)
258
+
259
+ if not self._auth_service:
260
+ logger.info("CIRIS_USER_CREATE: No auth service - skipping DB load")
261
+ return
262
+
263
+ try:
264
+ was = await self._auth_service.list_was(active_only=False)
265
+ logger.info(f"CIRIS_USER_CREATE: Loaded {len(was)} WA certificates from database")
266
+
267
+ for i, wa in enumerate(was, 1):
268
+ logger.info(
269
+ f"CIRIS_USER_CREATE: Processing WA {i}/{len(was)}: wa_id={wa.wa_id}, name={wa.name}, role={wa.role}"
270
+ )
271
+ await self._process_wa_record(wa)
272
+
273
+ # Check if we need to create a default admin
274
+ # Skip if:
275
+ # 1. Any user named 'admin' exists, OR
276
+ # 2. Any ROOT user exists (setup wizard creates ROOT user with custom name)
277
+ has_admin_user = any(u.name == "admin" for u in self._users.values())
278
+ has_root_user = any(u.wa_role == WARole.ROOT for u in self._users.values())
279
+
280
+ logger.info(f"CIRIS_USER_CREATE: Check default admin: has_admin={has_admin_user}, has_root={has_root_user}")
281
+
282
+ if not has_admin_user and not has_root_user:
283
+ logger.info("CIRIS_USER_CREATE: No admin/ROOT user found - will create default admin")
284
+ await self._create_default_admin()
285
+ else:
286
+ logger.info("CIRIS_USER_CREATE: Skipping default admin creation - admin or ROOT already exists")
287
+
288
+ # Clear the fallback admin if it wasn't loaded from the database
289
+ # The fallback admin is only meant for when there's no auth_service
290
+ # If wa-system-admin is in the DB, it's a real user and should be kept
291
+ loaded_wa_ids = {wa.wa_id for wa in was}
292
+ if "wa-system-admin" in self._users and "wa-system-admin" not in loaded_wa_ids:
293
+ logger.info("CIRIS_USER_CREATE: Removing fallback 'wa-system-admin' - not in DB, real users loaded")
294
+ del self._users["wa-system-admin"]
295
+
296
+ logger.info(f"CIRIS_USER_CREATE: User loading complete. Total users in cache: {len(self._users)}")
297
+ unique_users = {u.wa_id: u for u in self._users.values()}
298
+ for wa_id, user in unique_users.items():
299
+ logger.info(
300
+ f"CIRIS_USER_CREATE: - {wa_id}: name={user.name}, wa_role={user.wa_role}, api_role={user.api_role}"
301
+ )
302
+ logger.info("=" * 70)
303
+
304
+ except Exception as e:
305
+ logger.error(f"CIRIS_USER_CREATE: Error loading users from database: {e}", exc_info=True)
306
+ raise
307
+
308
+ async def _create_default_admin(self) -> None:
309
+ """Create the default admin user in the database.
310
+
311
+ NOTE: This is only called if no user named 'admin' exists in the database.
312
+ During first-run setup, the setup wizard creates the ROOT user, so this
313
+ should NOT be called in that flow.
314
+ """
315
+ if not self._auth_service:
316
+ logger.info("CIRIS_USER_CREATE: _create_default_admin skipped - no auth_service")
317
+ return
318
+
319
+ logger.info("=" * 70)
320
+ logger.info("CIRIS_USER_CREATE: _create_default_admin() called")
321
+ logger.info("=" * 70)
322
+
323
+ try:
324
+ # Check existing WAs before creating admin
325
+ existing_was = await self._auth_service.list_was(active_only=False)
326
+ logger.info(f"CIRIS_USER_CREATE: Existing WAs before default admin: {len(existing_was)}")
327
+ for wa in existing_was:
328
+ logger.info(f"CIRIS_USER_CREATE: - {wa.wa_id}: name={wa.name}, role={wa.role}")
329
+
330
+ # Check if any ROOT user already exists - DON'T create another one
331
+ root_was = [wa for wa in existing_was if wa.role == WARole.ROOT]
332
+ if root_was:
333
+ logger.info(
334
+ f"CIRIS_USER_CREATE: ROOT WA already exists ({root_was[0].wa_id}) - skipping default admin creation"
335
+ )
336
+ return
337
+
338
+ logger.info("CIRIS_USER_CREATE: No ROOT WA exists - creating default admin")
339
+
340
+ # Create admin WA certificate
341
+ wa_cert = await self._auth_service.create_wa(
342
+ name="admin",
343
+ email="admin@ciris.local",
344
+ scopes=["*"], # All permissions
345
+ role=WARole.ROOT, # System admin gets ROOT role
346
+ )
347
+ logger.info(f"CIRIS_USER_CREATE: ✅ Created default admin WA: {wa_cert.wa_id}")
348
+
349
+ # Update with password hash
350
+ await self._auth_service.update_wa(
351
+ wa_cert.wa_id, updates=None, password_hash=self._hash_password("ciris_admin_password")
352
+ )
353
+ logger.info(f"CIRIS_USER_CREATE: Password set for default admin: {wa_cert.wa_id}")
354
+
355
+ # Add to cache
356
+ admin_user = User(
357
+ wa_id=wa_cert.wa_id,
358
+ name="admin",
359
+ auth_type="password",
360
+ api_role=APIRole.SYSTEM_ADMIN,
361
+ wa_role=WARole.ROOT,
362
+ created_at=wa_cert.created_at,
363
+ is_active=True,
364
+ password_hash=self._hash_password("ciris_admin_password"),
365
+ )
366
+ self._users[admin_user.wa_id] = admin_user
367
+ logger.info(f"CIRIS_USER_CREATE: Added default admin to user cache")
368
+
369
+ except Exception as e:
370
+ logger.error(f"CIRIS_USER_CREATE: Error creating default admin: {e}", exc_info=True)
371
+
372
+ def _wa_role_to_api_role(self, wa_role: Optional[WARole]) -> APIRole:
373
+ """Convert WA role to API role."""
374
+ if not wa_role:
375
+ return APIRole.OBSERVER
376
+
377
+ role_map = {
378
+ WARole.ROOT: APIRole.SYSTEM_ADMIN,
379
+ WARole.AUTHORITY: APIRole.AUTHORITY,
380
+ WARole.OBSERVER: APIRole.OBSERVER,
381
+ }
382
+ return role_map.get(wa_role, APIRole.OBSERVER)
383
+
384
+ def _hash_key(self, api_key: str) -> str:
385
+ """Hash an API key for storage using bcrypt."""
386
+ # Use bcrypt for secure key hashing (same as passwords)
387
+ salt = bcrypt.gensalt(rounds=12)
388
+ hashed = bcrypt.hashpw(api_key.encode("utf-8"), salt)
389
+ return hashed.decode("utf-8")
390
+
391
+ def _verify_key(self, api_key: str, key_hash: str) -> bool:
392
+ """Verify an API key against its hash."""
393
+ try:
394
+ return bcrypt.checkpw(api_key.encode("utf-8"), key_hash.encode("utf-8"))
395
+ except Exception:
396
+ return False
397
+
398
+ def _get_key_id(self, api_key: str) -> str:
399
+ """Extract key ID from full API key."""
400
+ # Key format: ciris_role_randomstring
401
+ # Key ID is first 8 chars of SHA256 hash (for display purposes only)
402
+ return hashlib.sha256(api_key.encode()).hexdigest()[:8]
403
+
404
+ def store_api_key(
405
+ self,
406
+ key: str,
407
+ user_id: str,
408
+ role: UserRole,
409
+ expires_at: Optional[datetime] = None,
410
+ description: Optional[str] = None,
411
+ created_by: Optional[str] = None,
412
+ ) -> None:
413
+ """Store a new API key."""
414
+ key_hash = self._hash_key(key)
415
+ key_id = self._get_key_id(key)
416
+ stored_key = StoredAPIKey(
417
+ key_id=key_id,
418
+ key_hash=key_hash,
419
+ key_value=key[:12] + "..." + key[-4:], # Masked version
420
+ user_id=user_id,
421
+ role=role,
422
+ expires_at=expires_at,
423
+ description=description,
424
+ created_at=datetime.now(timezone.utc),
425
+ created_by=created_by or user_id,
426
+ last_used=None,
427
+ is_active=True,
428
+ )
429
+ # Store by key_id instead of hash (bcrypt hashes are unique per call)
430
+ self._api_keys[key_id] = stored_key
431
+ logger.debug(
432
+ f"[AUTH SERVICE DEBUG] store_api_key: Instance #{self._instance_id} - Stored key_id={key_id} for user={user_id}, role={role}. Total keys now: {len(self._api_keys)}, dict_id={id(self._api_keys)}"
433
+ )
434
+
435
+ def validate_api_key(self, api_key: str) -> Optional[StoredAPIKey]:
436
+ """Validate an API key and return its info."""
437
+ # Get the key_id for fast lookup
438
+ key_id = self._get_key_id(api_key)
439
+ stored_key = self._api_keys.get(key_id)
440
+
441
+ # DEBUG: Log validation attempt with minimal context (key_id only, no key content)
442
+ all_key_ids = list(self._api_keys.keys()) # NOSONAR - key IDs are hashes, not secrets
443
+ logger.debug(
444
+ f"[AUTH SERVICE DEBUG] validate_api_key: Instance #{self._instance_id} - Validating key_id={key_id}"
445
+ )
446
+ logger.debug(
447
+ f"[AUTH SERVICE DEBUG] validate_api_key: Instance #{self._instance_id} - _api_keys has {len(self._api_keys)} keys, dict_id={id(self._api_keys)}"
448
+ )
449
+ logger.debug(
450
+ f"[AUTH SERVICE DEBUG] validate_api_key: Instance #{self._instance_id} - stored_key found: {stored_key is not None}"
451
+ )
452
+
453
+ # Verify the key using bcrypt
454
+ if not stored_key or not stored_key.is_active:
455
+ logger.debug(
456
+ f"[AUTH SERVICE DEBUG] validate_api_key: Instance #{self._instance_id} - FAILED: key not found or inactive"
457
+ )
458
+ return None
459
+
460
+ if not self._verify_key(api_key, stored_key.key_hash):
461
+ logger.debug(
462
+ f"[AUTH SERVICE DEBUG] validate_api_key: Instance #{self._instance_id} - FAILED: bcrypt verification failed"
463
+ )
464
+ return None
465
+
466
+ # Check expiration
467
+ if stored_key.expires_at and stored_key.expires_at < datetime.now(timezone.utc):
468
+ logger.debug(f"[AUTH SERVICE DEBUG] validate_api_key: Instance #{self._instance_id} - FAILED: key expired")
469
+ return None
470
+
471
+ # Update last used
472
+ stored_key.last_used = datetime.now(timezone.utc)
473
+
474
+ # Ensure system admin user exists in _users
475
+ if stored_key.user_id == "wa-system-admin" and stored_key.user_id not in self._users:
476
+ # Re-create the system admin user
477
+ admin_user = User(
478
+ wa_id="wa-system-admin",
479
+ name="admin",
480
+ auth_type="password",
481
+ api_role=APIRole.SYSTEM_ADMIN,
482
+ wa_role=None,
483
+ created_at=datetime.now(timezone.utc),
484
+ is_active=True,
485
+ password_hash=self._hash_password("ciris_admin_password"),
486
+ )
487
+ self._users[admin_user.wa_id] = admin_user
488
+
489
+ logger.debug(
490
+ f"[AUTH SERVICE DEBUG] validate_api_key: Instance #{self._instance_id} - SUCCESS: key valid for user={stored_key.user_id}, role={stored_key.role}"
491
+ )
492
+ return stored_key
493
+
494
+ def revoke_api_key(self, key_id: str) -> None:
495
+ """Revoke an API key."""
496
+ # Lookup by key_id directly
497
+ stored_key = self._api_keys.get(key_id)
498
+ if stored_key:
499
+ stored_key.is_active = False
500
+
501
+ def create_oauth_user(
502
+ self,
503
+ provider: str,
504
+ external_id: str,
505
+ email: Optional[str],
506
+ name: Optional[str],
507
+ role: UserRole,
508
+ marketing_opt_in: bool = False,
509
+ ) -> OAuthUser:
510
+ """Create or update an OAuth user."""
511
+ user_id = f"{provider}:{external_id}"
512
+ now = datetime.now(timezone.utc)
513
+
514
+ if user_id in self._oauth_users:
515
+ # Update existing user
516
+ user = self._oauth_users[user_id]
517
+ user.last_login = now
518
+ if email:
519
+ user.email = email
520
+ if name:
521
+ user.name = name
522
+ # Update marketing opt-in (user can change their preference)
523
+ user.marketing_opt_in = marketing_opt_in
524
+ else:
525
+ # Create new user
526
+ user = OAuthUser(
527
+ user_id=user_id,
528
+ provider=provider,
529
+ external_id=external_id,
530
+ email=email,
531
+ name=name,
532
+ role=role,
533
+ created_at=now,
534
+ last_login=now,
535
+ marketing_opt_in=marketing_opt_in,
536
+ )
537
+ self._oauth_users[user_id] = user
538
+
539
+ return user
540
+
541
+ # ========== User Management Methods ==========
542
+
543
+ def _hash_password(self, password: str) -> str:
544
+ """Hash a password for storage using PBKDF2."""
545
+ import base64
546
+ import secrets
547
+
548
+ from cryptography.hazmat.primitives import hashes
549
+ from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
550
+
551
+ salt = secrets.token_bytes(32)
552
+ kdf = PBKDF2HMAC(
553
+ algorithm=hashes.SHA256(),
554
+ length=32,
555
+ salt=salt,
556
+ iterations=100000,
557
+ )
558
+ key = kdf.derive(password.encode())
559
+ return base64.b64encode(salt + key).decode()
560
+
561
+ def _verify_password(self, password: str, password_hash: str) -> bool:
562
+ """Verify a password against its hash using PBKDF2."""
563
+ try:
564
+ import base64
565
+ import hmac
566
+
567
+ from cryptography.hazmat.primitives import hashes
568
+ from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
569
+
570
+ decoded = base64.b64decode(password_hash)
571
+ salt = decoded[:32]
572
+ stored_key = decoded[32:]
573
+
574
+ kdf = PBKDF2HMAC(
575
+ algorithm=hashes.SHA256(),
576
+ length=32,
577
+ salt=salt,
578
+ iterations=100000,
579
+ )
580
+ key = kdf.derive(password.encode())
581
+ # Use constant-time comparison to prevent timing attacks
582
+ return hmac.compare_digest(key, stored_key)
583
+ except Exception:
584
+ # If verification fails (e.g., invalid hash format), return False
585
+ return False
586
+
587
+ async def verify_user_password(self, username: str, password: str) -> Optional[User]:
588
+ """Verify a user's password and return the user if valid."""
589
+ logger.debug("=" * 80)
590
+ logger.debug(f"[AUTH DEBUG] verify_user_password('{username}') called")
591
+ logger.debug(f"[AUTH DEBUG] _users_loaded flag: {self._users_loaded}")
592
+
593
+ # Ensure users are loaded from database
594
+ await self._ensure_users_loaded()
595
+
596
+ logger.debug(f"[AUTH DEBUG] After _ensure_users_loaded, _users_loaded: {self._users_loaded}")
597
+ logger.debug(f"[AUTH DEBUG] _users dict size: {len(self._users)}")
598
+
599
+ user = self.get_user_by_username(username)
600
+ if not user:
601
+ logger.debug("[AUTH DEBUG] User lookup failed - returning None")
602
+ logger.debug("=" * 80)
603
+ return None
604
+
605
+ logger.debug(f"[AUTH DEBUG] User found: wa_id={user.wa_id}")
606
+ logger.debug(f"[AUTH DEBUG] User.name: '{user.name}'")
607
+ logger.debug(f"[AUTH DEBUG] User.auth_type: '{user.auth_type}'")
608
+ logger.debug(f"[AUTH DEBUG] Has password_hash: {user.password_hash is not None}")
609
+
610
+ if user.password_hash:
611
+ logger.debug(f"[AUTH DEBUG] password_hash length: {len(user.password_hash)}")
612
+ logger.debug(f"[AUTH DEBUG] password_hash prefix: {user.password_hash[:10]}")
613
+
614
+ verify_result = self._verify_password(password, user.password_hash)
615
+ logger.debug(f"[AUTH DEBUG] Password verification result: {verify_result}")
616
+
617
+ if verify_result:
618
+ logger.debug(f"[AUTH DEBUG] Authentication SUCCESS for '{username}'")
619
+ logger.debug("=" * 80)
620
+ return user
621
+ else:
622
+ logger.debug("[AUTH DEBUG] Password verification FAILED")
623
+ logger.debug("=" * 80)
624
+ return None
625
+ else:
626
+ logger.debug("[AUTH DEBUG] No password_hash for user")
627
+ logger.debug("=" * 80)
628
+ return None
629
+
630
+ def get_user_by_username(self, username: str) -> Optional[User]:
631
+ """Get a user by username."""
632
+ logger.debug(f"[AUTH DEBUG] get_user_by_username('{username}') called")
633
+ logger.debug(f"[AUTH DEBUG] _users dict has {len(self._users)} entries")
634
+
635
+ # Get unique usernames (since users can be stored under multiple keys)
636
+ unique_users = {}
637
+ for key, user in self._users.items():
638
+ if user.wa_id not in unique_users:
639
+ unique_users[user.wa_id] = user
640
+
641
+ usernames = [u.name for u in unique_users.values()]
642
+ logger.debug(f"[AUTH DEBUG] Available usernames: {usernames}")
643
+
644
+ for user in self._users.values():
645
+ if user.name == username:
646
+ logger.debug(
647
+ f"[AUTH DEBUG] FOUND user: wa_id={user.wa_id}, name={user.name}, has_password={user.password_hash is not None}"
648
+ )
649
+ return user
650
+
651
+ logger.debug(f"[AUTH DEBUG] User '{username}' NOT FOUND")
652
+ return None
653
+
654
+ async def create_user(self, username: str, password: str, api_role: APIRole = APIRole.OBSERVER) -> Optional[User]:
655
+ """Create a new user account."""
656
+ # Check if username already exists
657
+ existing = self.get_user_by_username(username)
658
+ if existing:
659
+ return None
660
+
661
+ # Map API role to WA role
662
+ wa_role_map = {
663
+ APIRole.SYSTEM_ADMIN: WARole.ROOT,
664
+ APIRole.AUTHORITY: WARole.AUTHORITY,
665
+ APIRole.ADMIN: WARole.AUTHORITY, # Admin also gets AUTHORITY
666
+ APIRole.OBSERVER: WARole.OBSERVER,
667
+ }
668
+ wa_role = wa_role_map.get(api_role, WARole.OBSERVER)
669
+
670
+ # If we have an auth service, create in database
671
+ if self._auth_service:
672
+ try:
673
+ # Create WA certificate
674
+ wa_cert = await self._auth_service.create_wa(
675
+ name=username,
676
+ email=f"{username}@ciris.local",
677
+ scopes=self.get_permissions_for_role(api_role),
678
+ role=wa_role,
679
+ )
680
+
681
+ # Update with password hash
682
+ await self._auth_service.update_wa(
683
+ wa_cert.wa_id, updates=None, password_hash=self._hash_password(password)
684
+ )
685
+
686
+ # Create user object
687
+ user = User(
688
+ wa_id=wa_cert.wa_id,
689
+ name=username,
690
+ auth_type="password",
691
+ api_role=api_role,
692
+ wa_role=wa_role,
693
+ created_at=wa_cert.created_at,
694
+ is_active=True,
695
+ password_hash=self._hash_password(password),
696
+ )
697
+
698
+ # Store in cache
699
+ self._users[wa_cert.wa_id] = user
700
+ return user
701
+
702
+ except Exception as e:
703
+ logger.debug(f"[AUTH DEBUG] Error creating user in database: {e}")
704
+ # Fall through to in-memory creation
705
+
706
+ # Fallback: in-memory only
707
+ user_id = f"wa-user-{secrets.token_hex(8)}"
708
+ now = datetime.now(timezone.utc)
709
+ user = User(
710
+ wa_id=user_id,
711
+ name=username,
712
+ auth_type="password",
713
+ api_role=api_role,
714
+ created_at=now,
715
+ is_active=True,
716
+ password_hash=self._hash_password(password),
717
+ )
718
+
719
+ # Store user
720
+ self._users[user_id] = user
721
+
722
+ return user
723
+
724
+ async def list_users(
725
+ self,
726
+ search: Optional[str] = None,
727
+ auth_type: Optional[str] = None,
728
+ api_role: Optional[APIRole] = None,
729
+ wa_role: Optional[WARole] = None,
730
+ is_active: Optional[bool] = None,
731
+ ) -> List[tuple[str, User]]:
732
+ """List all users with optional filtering. Returns (user_id, user) tuples."""
733
+ # Ensure users are loaded from database before listing
734
+ await self._ensure_users_loaded()
735
+
736
+ users = []
737
+ seen_wa_ids: set[str] = set() # Dedupe by wa_id
738
+
739
+ # Add all stored users with their keys (deduplicated by wa_id)
740
+ for user_id, user in self._users.items():
741
+ # Skip duplicates - _users has multiple keys (wa_id, google:xxx) for same user
742
+ if user.wa_id in seen_wa_ids:
743
+ continue
744
+ seen_wa_ids.add(user.wa_id)
745
+
746
+ # Apply filters
747
+ if search and search.lower() not in user.name.lower():
748
+ continue
749
+ if auth_type and user.auth_type != auth_type:
750
+ continue
751
+ if api_role and user.api_role != api_role:
752
+ continue
753
+ if wa_role and user.wa_role != wa_role:
754
+ continue
755
+ if is_active is not None and user.is_active != is_active:
756
+ continue
757
+
758
+ users.append((user_id, user)) # Use the dict key as the canonical user_id
759
+
760
+ # Add OAuth users not in _users
761
+ for oauth_user in self._oauth_users.values():
762
+ oauth_user_id = oauth_user.user_id
763
+ # Check if already in users by matching oauth_external_id
764
+ # This handles cases where the DB WA has a different wa_id (e.g., wa-2025-12-03-xxx)
765
+ # but represents the same OAuth user (same oauth_external_id)
766
+ if any(uid == oauth_user_id or u.oauth_external_id == oauth_user.external_id for uid, u in users):
767
+ continue
768
+
769
+ # Convert OAuth user to User
770
+ user = User(
771
+ wa_id=oauth_user.user_id,
772
+ name=oauth_user.name or oauth_user.email or oauth_user.user_id,
773
+ auth_type="oauth",
774
+ api_role=self._user_role_to_api_role(oauth_user.role),
775
+ oauth_provider=oauth_user.provider,
776
+ oauth_email=oauth_user.email,
777
+ oauth_external_id=oauth_user.external_id,
778
+ oauth_name=oauth_user.name, # Map OAuth name to oauth_name field
779
+ created_at=oauth_user.created_at,
780
+ last_login=oauth_user.last_login,
781
+ is_active=True,
782
+ )
783
+
784
+ # Apply filters
785
+ if search and search.lower() not in user.name.lower():
786
+ continue
787
+ if auth_type and user.auth_type != auth_type:
788
+ continue
789
+ if api_role and user.api_role != api_role:
790
+ continue
791
+ if wa_role is not None:
792
+ continue # OAuth users without WA role
793
+ if is_active is not None and user.is_active != is_active:
794
+ continue
795
+
796
+ users.append((oauth_user_id, user))
797
+
798
+ return sorted(users, key=lambda tu: tu[1].created_at or datetime.min, reverse=True)
799
+
800
+ def _user_role_to_api_role(self, role: UserRole) -> APIRole:
801
+ """Convert UserRole to APIRole."""
802
+ mapping = {
803
+ UserRole.OBSERVER: APIRole.OBSERVER,
804
+ UserRole.ADMIN: APIRole.ADMIN,
805
+ UserRole.SYSTEM_ADMIN: APIRole.SYSTEM_ADMIN,
806
+ }
807
+ return mapping.get(role, APIRole.OBSERVER)
808
+
809
+ def _merge_oauth_with_stored_user(self, oauth_user: OAuthUser, stored_user: User) -> User:
810
+ """Merge OAuth session data with persistent database user data."""
811
+ return User(
812
+ wa_id=stored_user.wa_id, # Use the actual WA ID from database!
813
+ name=stored_user.name or oauth_user.name or oauth_user.email or oauth_user.user_id,
814
+ auth_type="oauth",
815
+ api_role=stored_user.api_role, # Preserve DB role
816
+ wa_role=stored_user.wa_role, # Preserve WA role
817
+ oauth_provider=oauth_user.provider,
818
+ oauth_email=oauth_user.email,
819
+ oauth_external_id=oauth_user.external_id,
820
+ created_at=stored_user.created_at or oauth_user.created_at,
821
+ last_login=oauth_user.last_login,
822
+ is_active=stored_user.is_active,
823
+ wa_parent_id=stored_user.wa_parent_id,
824
+ wa_auto_minted=stored_user.wa_auto_minted,
825
+ oauth_name=stored_user.oauth_name or oauth_user.name,
826
+ oauth_picture=stored_user.oauth_picture,
827
+ permission_requested_at=stored_user.permission_requested_at,
828
+ custom_permissions=stored_user.custom_permissions,
829
+ oauth_links=stored_user.oauth_links,
830
+ marketing_opt_in=oauth_user.marketing_opt_in,
831
+ )
832
+
833
+ def _create_user_from_oauth(self, oauth_user: OAuthUser) -> User:
834
+ """Create a User object from an OAuth user (not yet minted as WA)."""
835
+ return User(
836
+ wa_id=oauth_user.user_id, # OAuth user_id as placeholder
837
+ name=oauth_user.name or oauth_user.email or oauth_user.user_id,
838
+ auth_type="oauth",
839
+ api_role=self._user_role_to_api_role(oauth_user.role),
840
+ oauth_provider=oauth_user.provider,
841
+ oauth_email=oauth_user.email,
842
+ oauth_external_id=oauth_user.external_id,
843
+ created_at=oauth_user.created_at,
844
+ last_login=oauth_user.last_login,
845
+ is_active=True,
846
+ oauth_name=oauth_user.name,
847
+ marketing_opt_in=oauth_user.marketing_opt_in,
848
+ )
849
+
850
+ def _lookup_oauth_user(self, user_id: str) -> Optional[User]:
851
+ """Look up user in OAuth users dictionary."""
852
+ if user_id not in self._oauth_users:
853
+ return None
854
+
855
+ oauth_user = self._oauth_users[user_id]
856
+ stored_user = self._users.get(user_id)
857
+
858
+ # If stored_user exists, merge OAuth session data with persistent DB data
859
+ if stored_user:
860
+ return self._merge_oauth_with_stored_user(oauth_user, stored_user)
861
+
862
+ # No stored user - pure OAuth user not yet minted as WA
863
+ return self._create_user_from_oauth(oauth_user)
864
+
865
+ def _lookup_by_external_id(self, user_id: str) -> Optional[User]:
866
+ """Fallback lookup by OAuth external_id (without provider prefix)."""
867
+ # Try stored users first
868
+ for key, user in self._users.items():
869
+ if user.oauth_external_id == user_id:
870
+ return user
871
+
872
+ # Try OAuth users
873
+ for key, oauth_user in self._oauth_users.items():
874
+ if oauth_user.external_id == user_id:
875
+ stored_user = self._users.get(key)
876
+ if stored_user:
877
+ return stored_user
878
+ return self._create_user_from_oauth(oauth_user)
879
+
880
+ return None
881
+
882
+ def get_user(self, user_id: str) -> Optional[User]:
883
+ """Get a specific user by ID."""
884
+ # Check stored users first (includes users loaded from DB with OAuth links)
885
+ if user_id in self._users:
886
+ return self._users[user_id]
887
+
888
+ # Check OAuth users (in-memory only, for users who haven't been minted as WA yet)
889
+ oauth_result = self._lookup_oauth_user(user_id)
890
+ if oauth_result:
891
+ return oauth_result
892
+
893
+ # Fallback: Try to find user by OAuth external_id (without provider prefix)
894
+ # This handles cases where frontend passes just "googleUserId" without "google:" prefix
895
+ return self._lookup_by_external_id(user_id)
896
+
897
+ async def update_user(
898
+ self, user_id: str, api_role: Optional[APIRole] = None, is_active: Optional[bool] = None
899
+ ) -> Optional[User]:
900
+ """Update user information."""
901
+ user = self.get_user(user_id)
902
+ if not user:
903
+ return None
904
+
905
+ # Update fields
906
+ if api_role is not None:
907
+ user.api_role = api_role
908
+ # Also update WA role to match
909
+ wa_role_map = {
910
+ APIRole.SYSTEM_ADMIN: WARole.ROOT,
911
+ APIRole.AUTHORITY: WARole.AUTHORITY,
912
+ APIRole.ADMIN: WARole.AUTHORITY, # Admin also gets AUTHORITY
913
+ APIRole.OBSERVER: WARole.OBSERVER,
914
+ }
915
+ user.wa_role = wa_role_map.get(api_role, WARole.OBSERVER)
916
+ if is_active is not None:
917
+ user.is_active = is_active
918
+
919
+ # Store updated user
920
+ self._users[user_id] = user
921
+
922
+ # Also update in database if we have auth service
923
+ if self._auth_service:
924
+ try:
925
+ # Update role in database
926
+ if api_role is not None and user.wa_role:
927
+ await self._auth_service.update_wa(
928
+ user_id,
929
+ updates=WAUpdate(
930
+ role=user.wa_role.value if hasattr(user.wa_role, "value") else str(user.wa_role)
931
+ ),
932
+ )
933
+
934
+ # Update active status in database
935
+ if is_active is not None:
936
+ if is_active:
937
+ # Reactivate - note: this may not work if cert was revoked
938
+ await self._auth_service.update_wa(user_id, updates=WAUpdate(is_active=True))
939
+ else:
940
+ # Deactivate
941
+ await self._auth_service.revoke_wa(user_id, reason="User deactivated via API")
942
+ except Exception as e:
943
+ logger.debug(f"[AUTH DEBUG] Error updating user in database: {e}")
944
+
945
+ # Also update OAuth user if applicable
946
+ if user_id in self._oauth_users:
947
+ oauth_user = self._oauth_users[user_id]
948
+ if api_role is not None:
949
+ # Convert APIRole back to UserRole
950
+ role_mapping = {
951
+ APIRole.OBSERVER: UserRole.OBSERVER,
952
+ APIRole.ADMIN: UserRole.ADMIN,
953
+ APIRole.AUTHORITY: UserRole.ADMIN, # No direct mapping
954
+ APIRole.SYSTEM_ADMIN: UserRole.SYSTEM_ADMIN,
955
+ }
956
+ oauth_user.role = role_mapping.get(api_role, UserRole.OBSERVER)
957
+
958
+ return user
959
+
960
+ async def link_user_oauth(
961
+ self,
962
+ wa_id: str,
963
+ provider: str,
964
+ external_id: str,
965
+ *,
966
+ account_name: Optional[str] = None,
967
+ metadata: Optional[Dict[str, str]] = None,
968
+ primary: bool = False,
969
+ ) -> Optional[User]:
970
+ if not self._auth_service:
971
+ raise ValueError("Authentication service not configured")
972
+
973
+ updated = await self._auth_service.link_oauth_identity(
974
+ wa_id,
975
+ provider,
976
+ external_id,
977
+ account_name=account_name,
978
+ metadata=metadata,
979
+ primary=primary,
980
+ )
981
+
982
+ if not updated:
983
+ return None
984
+
985
+ await self._process_wa_record(updated)
986
+ return self.get_user(wa_id)
987
+
988
+ async def unlink_user_oauth(self, wa_id: str, provider: str, external_id: str) -> Optional[User]:
989
+ if not self._auth_service:
990
+ raise ValueError("Authentication service not configured")
991
+
992
+ updated = await self._auth_service.unlink_oauth_identity(wa_id, provider, external_id)
993
+ if not updated:
994
+ return None
995
+
996
+ await self._process_wa_record(updated)
997
+ return self.get_user(wa_id)
998
+
999
+ async def change_password(
1000
+ self, user_id: str, new_password: str, current_password: Optional[str] = None, skip_current_check: bool = False
1001
+ ) -> bool:
1002
+ """Change user password."""
1003
+ user = self.get_user(user_id)
1004
+ if not user or user.auth_type != "password":
1005
+ return False
1006
+
1007
+ # Verify current password unless skip_current_check is True
1008
+ if not skip_current_check and current_password:
1009
+ if not user.password_hash or not self._verify_password(current_password, user.password_hash):
1010
+ return False
1011
+
1012
+ # Update password
1013
+ user.password_hash = self._hash_password(new_password)
1014
+ self._users[user_id] = user
1015
+
1016
+ # Also update in database if we have auth service
1017
+ if self._auth_service:
1018
+ try:
1019
+ # Use await instead of asyncio.run() - we're already in an async context
1020
+ await self._auth_service.update_wa(
1021
+ user_id, updates=None, password_hash=self._hash_password(new_password)
1022
+ )
1023
+ except Exception as e:
1024
+ logger.debug(f"[AUTH DEBUG] Error updating password in database: {e}")
1025
+
1026
+ return True
1027
+
1028
+ async def deactivate_user(self, user_id: str) -> bool:
1029
+ """Deactivate a user account."""
1030
+ user = self.get_user(user_id)
1031
+ if not user:
1032
+ return False
1033
+
1034
+ user.is_active = False
1035
+ self._users[user_id] = user
1036
+
1037
+ # Also update in database if we have auth service
1038
+ if self._auth_service:
1039
+ try:
1040
+ await self._auth_service.revoke_wa(user_id, reason="User deactivated via API")
1041
+ except Exception as e:
1042
+ logger.debug(f"[AUTH DEBUG] Error deactivating user in database: {e}")
1043
+
1044
+ # Also deactivate OAuth user if applicable
1045
+ if user_id in self._oauth_users:
1046
+ # Can't really deactivate OAuth users in this simple implementation
1047
+ pass
1048
+
1049
+ return True
1050
+
1051
+ def get_permissions_for_role(self, role: APIRole) -> List[str]:
1052
+ """Get permissions for a given API role."""
1053
+ # Define role permissions
1054
+ permissions = {
1055
+ APIRole.OBSERVER: [
1056
+ PERMISSION_SYSTEM_READ,
1057
+ PERMISSION_MEMORY_READ,
1058
+ PERMISSION_TELEMETRY_READ,
1059
+ PERMISSION_CONFIG_READ,
1060
+ PERMISSION_AUDIT_READ,
1061
+ ],
1062
+ APIRole.ADMIN: [
1063
+ PERMISSION_SYSTEM_READ,
1064
+ PERMISSION_SYSTEM_WRITE,
1065
+ PERMISSION_MEMORY_READ,
1066
+ PERMISSION_MEMORY_WRITE,
1067
+ PERMISSION_TELEMETRY_READ,
1068
+ PERMISSION_CONFIG_READ,
1069
+ PERMISSION_CONFIG_WRITE,
1070
+ PERMISSION_AUDIT_READ,
1071
+ PERMISSION_AUDIT_WRITE,
1072
+ PERMISSION_USERS_READ,
1073
+ PERMISSION_MANAGE_USER_PERMISSIONS,
1074
+ ],
1075
+ APIRole.AUTHORITY: [
1076
+ PERMISSION_SYSTEM_READ,
1077
+ PERMISSION_SYSTEM_WRITE,
1078
+ PERMISSION_MEMORY_READ,
1079
+ PERMISSION_MEMORY_WRITE,
1080
+ PERMISSION_TELEMETRY_READ,
1081
+ PERMISSION_CONFIG_READ,
1082
+ PERMISSION_CONFIG_WRITE,
1083
+ PERMISSION_AUDIT_READ,
1084
+ PERMISSION_AUDIT_WRITE,
1085
+ PERMISSION_USERS_READ,
1086
+ PERMISSION_WA_READ,
1087
+ PERMISSION_WA_WRITE,
1088
+ "wa.resolve_deferral", # AUTHORITY role can resolve deferrals
1089
+ ],
1090
+ APIRole.SYSTEM_ADMIN: [
1091
+ PERMISSION_SYSTEM_READ,
1092
+ PERMISSION_SYSTEM_WRITE,
1093
+ PERMISSION_MEMORY_READ,
1094
+ PERMISSION_MEMORY_WRITE,
1095
+ PERMISSION_TELEMETRY_READ,
1096
+ PERMISSION_CONFIG_READ,
1097
+ PERMISSION_CONFIG_WRITE,
1098
+ PERMISSION_AUDIT_READ,
1099
+ PERMISSION_AUDIT_WRITE,
1100
+ PERMISSION_USERS_READ,
1101
+ PERMISSION_USERS_WRITE,
1102
+ PERMISSION_USERS_DELETE,
1103
+ PERMISSION_WA_READ,
1104
+ PERMISSION_WA_WRITE,
1105
+ PERMISSION_WA_MINT,
1106
+ PERMISSION_EMERGENCY_SHUTDOWN,
1107
+ PERMISSION_MANAGE_USER_PERMISSIONS,
1108
+ ],
1109
+ }
1110
+
1111
+ return permissions.get(role, [])
1112
+
1113
+ def get_effective_permissions(self, user: "User") -> List[str]:
1114
+ """Get effective permissions for a user including WA role inheritance.
1115
+
1116
+ This applies the following inheritance rules:
1117
+ - ROOT WA users get SYSTEM_ADMIN + AUTHORITY permissions
1118
+ - AUTHORITY WA users get their role's permissions (which include wa.resolve_deferral)
1119
+ - All other users get just their API role's permissions
1120
+ - Custom permissions are always added on top
1121
+ """
1122
+ # Start with base permissions from API role
1123
+ permissions_set = set(self.get_permissions_for_role(user.api_role))
1124
+
1125
+ # ROOT WA role inherits AUTHORITY permissions (for deferral resolution, etc.)
1126
+ # This is the key rule: ROOT maps to SYSTEM_ADMIN API role, but also gets AUTHORITY perms
1127
+ if user.wa_role == WARole.ROOT:
1128
+ authority_perms = self.get_permissions_for_role(APIRole.AUTHORITY)
1129
+ permissions_set.update(authority_perms)
1130
+
1131
+ # AUTHORITY WA role already has wa.resolve_deferral in their API role permissions
1132
+ # No extra inheritance needed since AUTHORITY maps to APIRole.AUTHORITY
1133
+
1134
+ # Add custom permissions
1135
+ if user.custom_permissions:
1136
+ permissions_set.update(user.custom_permissions)
1137
+
1138
+ return list(permissions_set)
1139
+
1140
+ async def update_user_permissions(self, user_id: str, permissions: List[str]) -> Optional[User]:
1141
+ """Update a user's custom permissions."""
1142
+ user = self.get_user(user_id)
1143
+ if not user:
1144
+ return None
1145
+
1146
+ # Update custom permissions
1147
+ user.custom_permissions = permissions
1148
+ self._users[user_id] = user
1149
+
1150
+ # Also update in database if we have auth service
1151
+ if self._auth_service:
1152
+ try:
1153
+
1154
+ # Update the WA certificate with custom permissions
1155
+ # Don't pass custom_permissions_json as a kwarg, it's not in the protocol
1156
+ # Instead, we should store this separately or handle it differently
1157
+ await self._auth_service.update_wa(
1158
+ user_id, updates=WAUpdate(permissions=permissions) if permissions else None
1159
+ )
1160
+ except Exception as e:
1161
+ logger.debug(f"[AUTH DEBUG] Error updating permissions in database: {e}")
1162
+
1163
+ return user
1164
+
1165
+ def validate_service_token(self, token: str) -> Optional[User]:
1166
+ """Validate a service token and return a service account user.
1167
+
1168
+ Service tokens are compared against CIRIS_SERVICE_TOKEN environment variable.
1169
+ Uses constant-time comparison to prevent timing attacks.
1170
+ """
1171
+ import hmac
1172
+ import os
1173
+
1174
+ # Get expected service token from environment
1175
+ expected_token = os.environ.get("CIRIS_SERVICE_TOKEN")
1176
+ if not expected_token:
1177
+ return None
1178
+
1179
+ # Use constant-time comparison
1180
+ if not hmac.compare_digest(token, expected_token):
1181
+ return None
1182
+
1183
+ # Create and return service account user
1184
+ return User(
1185
+ wa_id="service-account",
1186
+ name="Service Account",
1187
+ auth_type="service_token",
1188
+ api_role=APIRole.SERVICE_ACCOUNT,
1189
+ wa_role=None,
1190
+ created_at=datetime.now(timezone.utc),
1191
+ last_login=datetime.now(timezone.utc),
1192
+ is_active=True,
1193
+ custom_permissions=None,
1194
+ )
1195
+
1196
+ def list_user_api_keys(self, user_id: str) -> List[StoredAPIKey]:
1197
+ """List all API keys for a specific user."""
1198
+ keys = []
1199
+ for stored_key in self._api_keys.values():
1200
+ if stored_key.user_id == user_id:
1201
+ keys.append(stored_key)
1202
+ return sorted(keys, key=lambda k: k.created_at, reverse=True)
1203
+
1204
+ async def verify_root_signature(self, user_id: str, wa_role: WARole, signature: str) -> bool:
1205
+ """Verify a ROOT signature for WA minting.
1206
+
1207
+ The signature should be over the message:
1208
+ "MINT_WA:{user_id}:{wa_role}:{timestamp}"
1209
+
1210
+ Where timestamp is in ISO format.
1211
+ """
1212
+ import json
1213
+ from pathlib import Path
1214
+
1215
+ from cryptography.hazmat.primitives.asymmetric import ed25519
1216
+
1217
+ try:
1218
+ # Load ROOT public key from seed/
1219
+ root_pub_path = Path(__file__).parent.parent.parent.parent.parent.parent / "seed" / "root_pub.json"
1220
+ async with aiofiles.open(root_pub_path, "r") as f:
1221
+ content = await f.read()
1222
+ root_data = json.loads(content)
1223
+
1224
+ # Get the public key (base64url encoded)
1225
+ pubkey_b64 = root_data["pubkey"]
1226
+
1227
+ # Decode from base64url to bytes
1228
+ # Add padding if needed
1229
+ pubkey_b64_padded = pubkey_b64 + "=" * (4 - len(pubkey_b64) % 4)
1230
+ pubkey_bytes = base64.urlsafe_b64decode(pubkey_b64_padded)
1231
+
1232
+ # Create Ed25519 public key object
1233
+ public_key = ed25519.Ed25519PublicKey.from_public_bytes(pubkey_bytes)
1234
+
1235
+ # The signature should include a timestamp
1236
+ # For verification, we'll accept signatures from the last hour
1237
+ now = datetime.now(timezone.utc)
1238
+
1239
+ # Try multiple timestamp formats within the last hour
1240
+ for minutes_ago in range(0, 60, 1): # Check last 60 minutes
1241
+ timestamp = (now - timedelta(minutes=minutes_ago)).isoformat()
1242
+ message = f"MINT_WA:{user_id}:{wa_role.value}:{timestamp}"
1243
+
1244
+ try:
1245
+ # Decode signature from base64url
1246
+ sig_padded = signature + "=" * (4 - len(signature) % 4)
1247
+ sig_bytes = base64.urlsafe_b64decode(sig_padded)
1248
+
1249
+ # Verify signature
1250
+ public_key.verify(sig_bytes, message.encode())
1251
+
1252
+ # If we get here, signature is valid
1253
+ return True
1254
+ except Exception:
1255
+ # Try next timestamp
1256
+ continue
1257
+
1258
+ # Also try without timestamp for backwards compatibility
1259
+ message_no_ts = f"MINT_WA:{user_id}:{wa_role.value}"
1260
+
1261
+ # Try standard base64 first (what our signing script produces)
1262
+ try:
1263
+ sig_bytes = base64.b64decode(signature)
1264
+ public_key.verify(sig_bytes, message_no_ts.encode())
1265
+ return True
1266
+ except Exception:
1267
+ pass
1268
+
1269
+ # Try urlsafe base64
1270
+ try:
1271
+ sig_padded = signature + "=" * (4 - len(signature) % 4)
1272
+ sig_bytes = base64.urlsafe_b64decode(sig_padded)
1273
+ public_key.verify(sig_bytes, message_no_ts.encode())
1274
+ return True
1275
+ except Exception:
1276
+ pass
1277
+
1278
+ return False
1279
+
1280
+ except Exception as e:
1281
+ # Log error but don't expose internal details
1282
+ logger.debug(f"[AUTH DEBUG] Signature verification error: {e}")
1283
+ return False
1284
+
1285
+ def _update_user_wa_role(self, user: User, wa_role: WARole, minted_by: str) -> None:
1286
+ """Update user's WA role and related fields."""
1287
+ user.wa_role = wa_role
1288
+ user.wa_parent_id = minted_by
1289
+ user.wa_auto_minted = False
1290
+
1291
+ def _upgrade_api_role_if_needed(self, user: User, wa_role: WARole) -> None:
1292
+ """Upgrade user's API role if WA role requires higher access."""
1293
+ # ROOT and AUTHORITY WA roles both grant AUTHORITY API role
1294
+ if wa_role in (WARole.ROOT, WARole.AUTHORITY) and user.api_role.value < APIRole.AUTHORITY.value:
1295
+ user.api_role = APIRole.AUTHORITY
1296
+ elif wa_role == WARole.OBSERVER and user.api_role.value < APIRole.OBSERVER.value:
1297
+ user.api_role = APIRole.OBSERVER
1298
+
1299
+ async def _update_existing_wa(self, user_id: str, wa_role: WARole) -> None:
1300
+ """Update existing WA certificate."""
1301
+ if not self._auth_service:
1302
+ return
1303
+ await self._auth_service.update_wa(
1304
+ user_id, updates=WAUpdate(role=wa_role.value if hasattr(wa_role, "value") else str(wa_role))
1305
+ )
1306
+ logger.debug(f"[AUTH DEBUG] Updated existing WA {user_id} to role {wa_role}")
1307
+
1308
+ def _create_wa_email(self, user_name: str) -> str:
1309
+ """Create email for WA certificate."""
1310
+ return user_name + "@ciris.local" if "@" not in user_name else user_name
1311
+
1312
+ def _get_wa_permissions(self, user: User) -> List[str]:
1313
+ """Get permissions for WA certificate."""
1314
+ base_permissions = self.get_permissions_for_role(user.api_role)
1315
+ return base_permissions + [
1316
+ "wa.resolve_deferral", # Critical for deferral resolution
1317
+ "wa.mint", # Allow WA to mint others
1318
+ ]
1319
+
1320
+ async def _create_new_wa_for_oauth_user(self, user: User, user_id: str, wa_role: WARole) -> str:
1321
+ """Create new WA certificate for OAuth user and return the wa_id."""
1322
+ if not self._auth_service:
1323
+ raise ValueError("Authentication service not available")
1324
+
1325
+ wa_permissions = self._get_wa_permissions(user)
1326
+
1327
+ # Create WA certificate with proper wa_id format, but link to OAuth user
1328
+ import json
1329
+ from datetime import datetime, timezone
1330
+
1331
+ from ciris_engine.schemas.services.authority_core import WACertificate
1332
+
1333
+ timestamp = datetime.now(timezone.utc)
1334
+
1335
+ # Generate proper wa_id (format: wa-YYYY-MM-DD-XXXXXX)
1336
+ # Must match pattern: ^wa-\d{4}-\d{2}-\d{2}-[A-Z0-9]{6}$
1337
+ import secrets
1338
+
1339
+ wa_id = f"wa-{timestamp.strftime('%Y-%m-%d')}-{secrets.token_hex(3).upper()}"
1340
+ jwt_kid = f"wa-jwt-oauth-{wa_id[-6:].lower()}"
1341
+
1342
+ # Extract OAuth info from user_id (format: "provider:external_id")
1343
+ oauth_provider = None
1344
+ oauth_external_id = None
1345
+ if ":" in user_id:
1346
+ oauth_provider, oauth_external_id = user_id.split(":", 1)
1347
+
1348
+ # Create WA certificate with proper wa_id but linked to OAuth identity
1349
+ wa_cert = WACertificate(
1350
+ wa_id=wa_id, # Proper wa_id format
1351
+ name=user.name,
1352
+ role=wa_role,
1353
+ pubkey=f"oauth-{oauth_provider}-{oauth_external_id}" if oauth_provider else user_id,
1354
+ jwt_kid=jwt_kid,
1355
+ oauth_provider=oauth_provider,
1356
+ oauth_external_id=oauth_external_id,
1357
+ auto_minted=True,
1358
+ scopes_json=json.dumps(wa_permissions),
1359
+ created_at=timestamp,
1360
+ last_auth=timestamp,
1361
+ )
1362
+
1363
+ # Store WA certificate in database
1364
+ # Note: Accessing private method - ideally this would be a public API
1365
+ if hasattr(self._auth_service, "_store_wa_certificate"):
1366
+ store_method = getattr(self._auth_service, "_store_wa_certificate")
1367
+ await store_method(wa_cert)
1368
+ else:
1369
+ raise ValueError("Cannot store WA certificate - method not available")
1370
+
1371
+ logger.debug(f"[AUTH DEBUG] Created WA certificate {wa_id} for OAuth user {user_id} with role {wa_role}")
1372
+ return wa_id
1373
+
1374
+ # Removed _link_oauth_identity - no longer needed since OAuth users use their user_id as wa_id
1375
+
1376
+ def _update_user_records_for_oauth_wa(self, user: User, user_id: str, wa_id: str) -> None:
1377
+ """Update OAuth user record with WA information (no duplicate records)."""
1378
+ user.wa_id = wa_id # Set the proper WA ID
1379
+ # Keep the user under their original OAuth user_id key
1380
+ self._users[user_id] = user # Update existing record, don't create duplicate
1381
+
1382
+ async def _handle_wa_database_operations(self, user: User, user_id: str, wa_role: WARole) -> None:
1383
+ """Handle WA database create/update operations."""
1384
+ if not self._auth_service:
1385
+ return
1386
+ existing_wa = await self._auth_service.get_wa(user_id)
1387
+
1388
+ if existing_wa:
1389
+ await self._update_existing_wa(user_id, wa_role)
1390
+ else:
1391
+ # For OAuth users, create WA with proper wa_id but update existing OAuth user record
1392
+ wa_id = await self._create_new_wa_for_oauth_user(user, user_id, wa_role)
1393
+ self._update_user_records_for_oauth_wa(user, user_id, wa_id)
1394
+
1395
+ async def mint_wise_authority(self, user_id: str, wa_role: WARole, minted_by: str) -> Optional[User]:
1396
+ """Mint a user as a Wise Authority."""
1397
+ user = self.get_user(user_id)
1398
+ if not user:
1399
+ return None
1400
+
1401
+ # Update user WA role and API role if needed
1402
+ self._update_user_wa_role(user, wa_role, minted_by)
1403
+ self._upgrade_api_role_if_needed(user, wa_role)
1404
+
1405
+ # Store updated user
1406
+ self._users[user_id] = user
1407
+
1408
+ # Handle database operations if auth service is available
1409
+ if self._auth_service:
1410
+ try:
1411
+ await self._handle_wa_database_operations(user, user_id, wa_role)
1412
+ # Note: parent_wa_id and auto_minted are not supported by the protocol's update_wa method
1413
+ # They would need to be set during creation or via a different mechanism
1414
+ except Exception as e:
1415
+ logger.debug(f"[AUTH DEBUG] Error updating/creating WA in database: {e}")
1416
+
1417
+ return user