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,1634 @@
1
+ """WA Authentication Service - Core authentication logic implementation."""
2
+
3
+ import asyncio
4
+ import base64
5
+ import functools
6
+ import hashlib
7
+ import inspect
8
+ import json
9
+ import logging
10
+ import os
11
+ import secrets
12
+ from datetime import datetime, timezone
13
+ from pathlib import Path
14
+ from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple, TypeVar, Union, cast
15
+
16
+ import aiofiles
17
+ import jwt
18
+ from cryptography.exceptions import InvalidSignature
19
+ from cryptography.hazmat.backends import default_backend
20
+ from cryptography.hazmat.primitives import hashes, serialization
21
+ from cryptography.hazmat.primitives.asymmetric import ed25519
22
+ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
23
+ from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
24
+
25
+ from ciris_engine.logic.persistence.stores import authentication_store
26
+ from ciris_engine.logic.services.base_infrastructure_service import BaseInfrastructureService
27
+ from ciris_engine.logic.services.lifecycle.time import TimeService
28
+ from ciris_engine.protocols.services.infrastructure.authentication import AuthenticationServiceProtocol
29
+ from ciris_engine.schemas.runtime.enums import ServiceType
30
+ from ciris_engine.schemas.services.authority.wise_authority import AuthenticationResult, TokenVerification, WAUpdate
31
+ from ciris_engine.schemas.services.authority_core import (
32
+ AuthorizationContext,
33
+ ChannelIdentity,
34
+ JWTSubType,
35
+ OAuthIdentityLink,
36
+ TokenType,
37
+ WACertificate,
38
+ WARole,
39
+ )
40
+ from ciris_engine.schemas.services.core import ServiceCapabilities, ServiceStatus
41
+ from ciris_engine.schemas.types import JSONDict
42
+
43
+ if TYPE_CHECKING:
44
+ from ciris_engine.schemas.runtime.models import Task
45
+
46
+ logger = logging.getLogger(__name__)
47
+
48
+ # Type variable for decorators
49
+ F = TypeVar("F", bound=Callable[..., Any])
50
+
51
+
52
+ class AuthenticationService(BaseInfrastructureService, AuthenticationServiceProtocol):
53
+ """Infrastructure service for WA authentication and identity management."""
54
+
55
+ def __init__(self, db_path: str, time_service: TimeService, key_dir: Optional[str] = None) -> None:
56
+ """Initialize the WA Authentication Service.
57
+
58
+ Args:
59
+ db_path: Path to SQLite database
60
+ time_service: TimeService instance for time operations (required)
61
+ key_dir: Directory for key storage (defaults to ~/.ciris/)
62
+ """
63
+ super().__init__() # Initialize BaseService
64
+ self.db_path = db_path
65
+ self.key_dir = Path(key_dir or os.path.expanduser("~/.ciris"))
66
+ self.key_dir.mkdir(mode=0o700, exist_ok=True)
67
+
68
+ # Store injected time service
69
+ self._time_service = time_service
70
+
71
+ # Initialize gateway secret
72
+ self.gateway_secret = self._get_or_create_gateway_secret()
73
+
74
+ # Cache for tokens and WAs
75
+ self._token_cache: Dict[str, AuthorizationContext] = {}
76
+ self._channel_token_cache: Dict[str, str] = {}
77
+
78
+ # Initialize database
79
+ self._init_database()
80
+
81
+ # Track service state
82
+ self._started = False
83
+ self._start_time: Optional[datetime] = None
84
+
85
+ # Authentication metrics tracking
86
+ self._auth_attempts = 0
87
+ self._auth_successes = 0
88
+ self._auth_failures = 0
89
+ self._token_validations = 0
90
+ self._permission_checks = 0
91
+ self._role_assignments = 0
92
+ self._session_count = 0
93
+ self._expired_sessions = 0
94
+ self._active_tokens = 0
95
+ self._revoked_tokens = 0
96
+
97
+ def get_service_type(self) -> ServiceType:
98
+ """Get service type - authentication is part of wise authority infrastructure."""
99
+ from ciris_engine.schemas.runtime.enums import ServiceType
100
+
101
+ return ServiceType.WISE_AUTHORITY
102
+
103
+ def _get_actions(self) -> List[str]:
104
+ """Get list of actions this service provides."""
105
+ return [
106
+ # Authentication operations
107
+ "authenticate",
108
+ "create_token",
109
+ "verify_token",
110
+ "verify_token_sync",
111
+ "create_channel_token",
112
+ # WA management
113
+ "create_wa",
114
+ "get_wa",
115
+ "update_wa",
116
+ "revoke_wa",
117
+ "list_was",
118
+ "rotate_keys",
119
+ # Utility operations
120
+ "bootstrap_if_needed",
121
+ "update_last_login",
122
+ "sign_task",
123
+ "verify_task_signature",
124
+ # Key operations
125
+ "generate_keypair",
126
+ "sign_data",
127
+ "hash_password",
128
+ ]
129
+
130
+ def _check_dependencies(self) -> bool:
131
+ """Check if all required dependencies are available."""
132
+ # Only requires time service which is provided in __init__
133
+ return self._time_service is not None
134
+
135
+ @staticmethod
136
+ def _encode_public_key(pubkey_bytes: bytes) -> str:
137
+ """Encode public key using base64url without padding."""
138
+ return base64.urlsafe_b64encode(pubkey_bytes).decode().rstrip("=")
139
+
140
+ @staticmethod
141
+ def _decode_public_key(pubkey_str: str) -> bytes:
142
+ """Decode base64url encoded public key, adding padding if needed."""
143
+ # Add padding if necessary
144
+ padding = 4 - (len(pubkey_str) % 4)
145
+ if padding != 4:
146
+ pubkey_str += "=" * padding
147
+ return base64.urlsafe_b64decode(pubkey_str)
148
+
149
+ def _derive_encryption_key(self, salt: bytes) -> bytes:
150
+ """Derive an encryption key from machine-specific data.
151
+
152
+ Args:
153
+ salt: Random salt for key derivation
154
+
155
+ Returns:
156
+ 32-byte derived encryption key
157
+ """
158
+ # Use machine ID and hostname as key material
159
+ machine_id = ""
160
+ hostname = ""
161
+
162
+ try:
163
+ # Try to get machine ID (Linux)
164
+ machine_id_path = Path("/etc/machine-id")
165
+ if machine_id_path.exists():
166
+ machine_id = machine_id_path.read_text().strip()
167
+ else:
168
+ # Fallback to hostname
169
+ import socket
170
+
171
+ hostname = socket.gethostname()
172
+ except Exception:
173
+ hostname = "default"
174
+
175
+ # Combine machine-specific data with purpose identifier
176
+ key_material = f"{machine_id}:{hostname}:gateway-secret-encryption".encode()
177
+
178
+ # Use PBKDF2 to derive a 32-byte key with the provided salt
179
+ kdf = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32, salt=salt, iterations=100000, backend=default_backend())
180
+ return kdf.derive(key_material)
181
+
182
+ def _encrypt_secret(self, secret: bytes) -> bytes:
183
+ """Encrypt a secret using AES-GCM with random salt.
184
+
185
+ Format: salt (32 bytes) + nonce (12 bytes) + ciphertext + tag (16 bytes)
186
+ """
187
+ # Generate random salt for key derivation
188
+ salt = os.urandom(32)
189
+
190
+ # Derive encryption key with the salt
191
+ key = self._derive_encryption_key(salt)
192
+
193
+ # Generate a random 96-bit nonce for GCM
194
+ nonce = os.urandom(12)
195
+
196
+ # Create cipher
197
+ cipher = Cipher(algorithms.AES(key), modes.GCM(nonce), backend=default_backend())
198
+ encryptor = cipher.encryptor()
199
+
200
+ # Encrypt and get tag
201
+ ciphertext = encryptor.update(secret) + encryptor.finalize()
202
+
203
+ # Return salt + nonce + ciphertext + tag
204
+ return salt + nonce + ciphertext + encryptor.tag
205
+
206
+ def _decrypt_secret(self, encrypted: bytes) -> bytes:
207
+ """Decrypt a secret using AES-GCM.
208
+
209
+ Expected format: salt (32 bytes) + nonce (12 bytes) + ciphertext + tag (16 bytes)
210
+ """
211
+ try:
212
+ # Check minimum length: salt(32) + nonce(12) + tag(16) = 60 bytes minimum
213
+ if len(encrypted) < 60:
214
+ # Handle legacy format without salt for backward compatibility
215
+ # Legacy format: nonce (12 bytes) + ciphertext + tag (16 bytes)
216
+ legacy_salt = b"ciris-gateway-encryption-salt"
217
+ key = self._derive_encryption_key(legacy_salt)
218
+
219
+ nonce = encrypted[:12]
220
+ tag = encrypted[-16:]
221
+ ciphertext = encrypted[12:-16]
222
+
223
+ cipher = Cipher(algorithms.AES(key), modes.GCM(nonce, tag), backend=default_backend())
224
+ decryptor = cipher.decryptor()
225
+ return decryptor.update(ciphertext) + decryptor.finalize()
226
+ else:
227
+ # Extract components for new format
228
+ salt = encrypted[:32]
229
+ nonce = encrypted[32:44]
230
+ tag = encrypted[-16:]
231
+ ciphertext = encrypted[44:-16]
232
+
233
+ # Derive key with extracted salt
234
+ key = self._derive_encryption_key(salt)
235
+
236
+ # Create cipher
237
+ cipher = Cipher(algorithms.AES(key), modes.GCM(nonce, tag), backend=default_backend())
238
+ decryptor = cipher.decryptor()
239
+
240
+ # Decrypt
241
+ return decryptor.update(ciphertext) + decryptor.finalize()
242
+ except Exception as e:
243
+ # Log the actual error for debugging (not exposed to caller)
244
+ logger.debug(f"Decryption failed: {type(e).__name__}: {e}")
245
+ # Always raise consistent error regardless of format or failure type
246
+ raise ValueError("Invalid encrypted data format")
247
+
248
+ def _get_or_create_gateway_secret(self) -> bytes:
249
+ """Get or create the gateway secret for JWT signing."""
250
+ secret_path = self.key_dir / "gateway.secret"
251
+ encrypted_path = self.key_dir / "gateway.secret.enc"
252
+
253
+ # Try to load existing encrypted secret first
254
+ if encrypted_path.exists():
255
+ try:
256
+ encrypted = encrypted_path.read_bytes()
257
+ return self._decrypt_secret(encrypted)
258
+ except Exception as e:
259
+ logger.warning(f"Failed to decrypt gateway secret: {type(e).__name__}")
260
+ # Fall through to regenerate
261
+
262
+ # Check for legacy unencrypted secret
263
+ if secret_path.exists():
264
+ # Read and encrypt the existing secret
265
+ secret = secret_path.read_bytes()
266
+ encrypted = self._encrypt_secret(secret)
267
+ encrypted_path.write_bytes(encrypted)
268
+ encrypted_path.chmod(0o600)
269
+ # Remove the unencrypted version
270
+ secret_path.unlink()
271
+ return secret
272
+
273
+ # Generate new 32-byte secret
274
+ secret = secrets.token_bytes(32)
275
+ encrypted = self._encrypt_secret(secret)
276
+ encrypted_path.write_bytes(encrypted)
277
+ encrypted_path.chmod(0o600)
278
+ return secret
279
+
280
+ def _init_database(self) -> None:
281
+ """Initialize database tables if needed."""
282
+ authentication_store.init_auth_database(self.db_path)
283
+
284
+ def _row_to_wa(self, row_dict: JSONDict) -> WACertificate:
285
+ """Convert a SQLite row dictionary to a WACertificate instance."""
286
+
287
+ oauth_links_json = row_dict.get("oauth_links_json")
288
+ oauth_links: List[OAuthIdentityLink] = []
289
+ if oauth_links_json:
290
+ try:
291
+ # Type narrow: json.loads expects str, not the broader JSONDict value type
292
+ if isinstance(oauth_links_json, str):
293
+ raw_links = json.loads(oauth_links_json)
294
+ for link in raw_links:
295
+ try:
296
+ oauth_links.append(OAuthIdentityLink(**link))
297
+ except Exception as exc:
298
+ logger.warning("Invalid OAuth link entry skipped: %s", exc)
299
+ except json.JSONDecodeError:
300
+ logger.warning("Invalid oauth_links_json for WA %s", row_dict.get("wa_id"))
301
+
302
+ wa_dict = {
303
+ "wa_id": row_dict["wa_id"],
304
+ "name": row_dict["name"],
305
+ "role": row_dict["role"],
306
+ "pubkey": row_dict["pubkey"],
307
+ "jwt_kid": row_dict["jwt_kid"],
308
+ "password_hash": row_dict.get("password_hash"),
309
+ "api_key_hash": row_dict.get("api_key_hash"),
310
+ "oauth_provider": row_dict.get("oauth_provider"),
311
+ "oauth_external_id": row_dict.get("oauth_external_id"),
312
+ "oauth_links": oauth_links,
313
+ "auto_minted": bool(row_dict.get("auto_minted", 0)),
314
+ "veilid_id": row_dict.get("veilid_id"),
315
+ "parent_wa_id": row_dict.get("parent_wa_id"),
316
+ "parent_signature": row_dict.get("parent_signature"),
317
+ "scopes_json": row_dict["scopes_json"],
318
+ "custom_permissions_json": row_dict.get("custom_permissions_json"),
319
+ "adapter_id": row_dict.get("adapter_id"),
320
+ "adapter_name": row_dict.get("adapter_name"),
321
+ "adapter_metadata_json": row_dict.get("adapter_metadata_json"),
322
+ "created_at": row_dict["created"],
323
+ "last_auth": row_dict.get("last_login"),
324
+ }
325
+
326
+ return WACertificate(**wa_dict)
327
+
328
+ # WAStore Protocol Implementation
329
+
330
+ async def get_wa(self, wa_id: str) -> Optional[WACertificate]:
331
+ """Get WA certificate by ID."""
332
+ return authentication_store.get_wa_by_id(wa_id, self.db_path)
333
+
334
+ async def _get_wa_by_kid(self, jwt_kid: str) -> Optional[WACertificate]:
335
+ """Get WA certificate by JWT key ID."""
336
+ return authentication_store.get_wa_by_kid(jwt_kid, self.db_path)
337
+
338
+ async def get_wa_by_oauth(self, provider: str, external_id: str) -> Optional[WACertificate]:
339
+ """Get WA certificate by OAuth identity."""
340
+ return authentication_store.get_wa_by_oauth(provider, external_id, self.db_path)
341
+
342
+ async def _get_wa_by_adapter(self, adapter_id: str) -> Optional[WACertificate]:
343
+ """Get WA certificate by adapter ID."""
344
+ return authentication_store.get_wa_by_adapter(adapter_id, self.db_path)
345
+
346
+ async def link_oauth_identity(
347
+ self,
348
+ wa_id: str,
349
+ provider: str,
350
+ external_id: str,
351
+ *,
352
+ account_name: Optional[str] = None,
353
+ metadata: Optional[Dict[str, str]] = None,
354
+ primary: bool = False,
355
+ ) -> Optional[WACertificate]:
356
+ existing = await self.get_wa(wa_id)
357
+ if not existing:
358
+ return None
359
+
360
+ # Prevent linking to a provider that already belongs to another WA
361
+ match = await self.get_wa_by_oauth(provider, external_id)
362
+ if match and match.wa_id != wa_id:
363
+ raise ValueError(f"OAuth identity {provider}:{external_id} already linked to another WA")
364
+
365
+ links = list(existing.oauth_links)
366
+ timestamp = self._time_service.now() if self._time_service else datetime.now(timezone.utc)
367
+ found = False
368
+ for idx, link in enumerate(links):
369
+ if link.provider == provider and link.external_id == external_id:
370
+ links[idx] = link.model_copy(
371
+ update={
372
+ "account_name": account_name or link.account_name,
373
+ "metadata": metadata or link.metadata,
374
+ "linked_at": link.linked_at or timestamp,
375
+ }
376
+ )
377
+ found = True
378
+ break
379
+
380
+ if not found:
381
+ links.append(
382
+ OAuthIdentityLink(
383
+ provider=provider,
384
+ external_id=external_id,
385
+ account_name=account_name,
386
+ metadata=metadata or {},
387
+ linked_at=timestamp,
388
+ is_primary=False,
389
+ )
390
+ )
391
+
392
+ # Add identity mapping to graph for DSAR coordination
393
+ # This creates a "same_as" edge between wa_id and oauth provider:external_id
394
+ if hasattr(self, "_memory_bus") and self._memory_bus:
395
+ try:
396
+ from ciris_engine.logic.utils.identity_resolution import add_identity_mapping
397
+
398
+ # Map wa_id to OAuth external_id in identity graph
399
+ await add_identity_mapping(
400
+ wa_id,
401
+ "wa_id",
402
+ external_id,
403
+ f"{provider}_id",
404
+ self._memory_bus,
405
+ confidence=1.0,
406
+ source="oauth",
407
+ )
408
+ logger.info(
409
+ f"Created identity mapping: wa_id:{wa_id} -> {provider}_id:{external_id}"
410
+ ) # NOSONAR - IDs not secrets
411
+ except Exception as e:
412
+ logger.warning(f"Failed to create identity mapping for OAuth link: {e}")
413
+ # Non-fatal - OAuth link still works even if graph mapping fails
414
+
415
+ if primary or (not existing.oauth_provider and not existing.oauth_external_id):
416
+ existing = existing.model_copy(update={"oauth_provider": provider, "oauth_external_id": external_id})
417
+ for link in links:
418
+ link.is_primary = link.provider == provider and link.external_id == external_id
419
+
420
+ # Build update kwargs, filtering out None values
421
+ update_kwargs = {}
422
+ if existing.oauth_provider:
423
+ update_kwargs["oauth_provider"] = existing.oauth_provider
424
+ if existing.oauth_external_id:
425
+ update_kwargs["oauth_external_id"] = existing.oauth_external_id
426
+ oauth_links_json = json.dumps([link.model_dump(mode="json") for link in links]) if links else None
427
+ if oauth_links_json:
428
+ update_kwargs["oauth_links_json"] = oauth_links_json
429
+
430
+ await self.update_wa(wa_id, **update_kwargs) # type: ignore[arg-type]
431
+ return await self.get_wa(wa_id)
432
+
433
+ async def unlink_oauth_identity(self, wa_id: str, provider: str, external_id: str) -> Optional[WACertificate]:
434
+ existing = await self.get_wa(wa_id)
435
+ if not existing:
436
+ return None
437
+
438
+ links = [
439
+ link for link in existing.oauth_links if not (link.provider == provider and link.external_id == external_id)
440
+ ]
441
+
442
+ payload: JSONDict = {
443
+ "oauth_links_json": json.dumps([link.model_dump(mode="json") for link in links]) if links else None,
444
+ }
445
+
446
+ # If unlinking the primary mapping, fall back to remaining links or clear
447
+ if existing.oauth_provider == provider and existing.oauth_external_id == external_id:
448
+ if links:
449
+ new_primary = next((link for link in links if link.is_primary), links[0])
450
+ payload["oauth_provider"] = new_primary.provider
451
+ payload["oauth_external_id"] = new_primary.external_id
452
+ for link in links:
453
+ link.is_primary = link is new_primary
454
+ payload["oauth_links_json"] = json.dumps([link.model_dump(mode="json") for link in links])
455
+ else:
456
+ payload["oauth_provider"] = None
457
+ payload["oauth_external_id"] = None
458
+
459
+ # Filter out None values - update_wa kwargs expects Union[str, bool, datetime], not Optional
460
+ filtered_payload = {k: v for k, v in payload.items() if v is not None}
461
+ await self.update_wa(wa_id, **filtered_payload) # type: ignore[arg-type]
462
+ return await self.get_wa(wa_id)
463
+
464
+ async def _store_wa_certificate(self, wa: WACertificate) -> None:
465
+ """Store a WA certificate in the database."""
466
+ authentication_store.store_wa_certificate(wa, self.db_path)
467
+
468
+ async def _create_adapter_observer(self, adapter_id: str, name: str) -> WACertificate:
469
+ """Create or reactivate adapter observer WA."""
470
+ # Check if observer already exists
471
+ existing = await self._get_wa_by_adapter(adapter_id)
472
+ if existing:
473
+ # Observer already exists and is active (since _get_wa_by_adapter only returns active ones)
474
+ return existing
475
+
476
+ # Generate new observer WA
477
+ private_key, public_key = self.generate_keypair()
478
+ timestamp = self._time_service.now() if self._time_service else datetime.now(timezone.utc)
479
+ wa_id = self._generate_wa_id(timestamp)
480
+ jwt_kid = f"wa-jwt-{wa_id[-6:].lower()}"
481
+
482
+ observer = WACertificate(
483
+ wa_id=wa_id,
484
+ name=name,
485
+ role=WARole.OBSERVER,
486
+ pubkey=self._encode_public_key(public_key),
487
+ jwt_kid=jwt_kid,
488
+ scopes_json='["read:any", "write:message"]',
489
+ adapter_id=adapter_id,
490
+ created_at=timestamp,
491
+ )
492
+
493
+ await self._store_wa_certificate(observer)
494
+ return observer
495
+
496
+ async def update_wa(
497
+ self, wa_id: str, updates: Optional[WAUpdate] = None, **kwargs: Union[str, bool, datetime]
498
+ ) -> Optional[WACertificate]:
499
+ """Update WA certificate fields."""
500
+ if updates:
501
+ # Convert WAUpdate to kwargs
502
+ update_kwargs = {}
503
+ if updates.name:
504
+ update_kwargs["name"] = updates.name
505
+ if updates.role:
506
+ update_kwargs["role"] = updates.role
507
+ if updates.permissions:
508
+ update_kwargs["scopes_json"] = json.dumps(updates.permissions)
509
+ if updates.metadata:
510
+ update_kwargs["metadata"] = json.dumps(updates.metadata)
511
+ if updates.is_active is not None:
512
+ update_kwargs["active"] = str(int(updates.is_active))
513
+ kwargs.update(update_kwargs)
514
+ if not kwargs:
515
+ return await self.get_wa(wa_id)
516
+
517
+ # Update via store
518
+ authentication_store.update_wa_certificate(wa_id, kwargs, self.db_path)
519
+
520
+ # Return updated WA
521
+ return await self.get_wa(wa_id)
522
+
523
+ async def revoke_wa(self, wa_id: str, reason: str) -> bool:
524
+ """Revoke WA certificate."""
525
+ # First check if the WA exists
526
+ existing = await self.get_wa(wa_id)
527
+ if not existing:
528
+ return False
529
+
530
+ # Update to set active=False
531
+ await self.update_wa(wa_id, active=False)
532
+
533
+ # Add audit log entry for revocation
534
+ if hasattr(self, "_audit_service") and self._audit_service:
535
+ await self._audit_service.log_event(
536
+ event_type="wa_revocation",
537
+ source_service="authentication",
538
+ details={
539
+ "wa_id": wa_id,
540
+ "reason": reason,
541
+ "timestamp": self._time_service.now().isoformat() if self._time_service else None,
542
+ },
543
+ )
544
+ logger.info(f"Revoked WA {wa_id}: {reason}")
545
+ return True
546
+
547
+ async def _list_all_was(self, active_only: bool = True) -> List[WACertificate]:
548
+ """List all WA certificates."""
549
+ return authentication_store.list_wa_certificates(active_only, self.db_path)
550
+
551
+ async def update_last_login(self, wa_id: str) -> None:
552
+ """Update last login timestamp."""
553
+ await self.update_wa(
554
+ wa_id, last_login=self._time_service.now() if self._time_service else datetime.now(timezone.utc)
555
+ )
556
+
557
+ # JWTService Protocol Implementation
558
+
559
+ async def create_channel_token(self, wa_id: str, channel_id: str, ttl: int = 3600) -> str:
560
+ """Create channel-specific token (for observers, creates long-lived adapter tokens)."""
561
+ # Get the WA certificate
562
+ wa = await self.get_wa(wa_id)
563
+ if not wa:
564
+ raise ValueError(f"WA {wa_id} not found")
565
+
566
+ payload = {
567
+ "sub": wa.wa_id,
568
+ "sub_type": JWTSubType.ANON.value,
569
+ "name": wa.name,
570
+ "scope": wa.scopes,
571
+ "iat": int(
572
+ self._time_service.timestamp() if self._time_service else datetime.now(timezone.utc).timestamp()
573
+ ),
574
+ }
575
+
576
+ # For observer tokens, use adapter_id and make them long-lived (no expiry)
577
+ if wa.role == WARole.OBSERVER and wa.adapter_id:
578
+ payload["adapter"] = wa.adapter_id
579
+ # No expiry for observer tokens by default
580
+ if ttl > 0:
581
+ # Only add expiry if explicitly requested
582
+ payload["exp"] = (
583
+ int(
584
+ self._time_service.timestamp() if self._time_service else datetime.now(timezone.utc).timestamp()
585
+ )
586
+ + ttl
587
+ )
588
+ else:
589
+ # For non-observer tokens, include channel and expiry
590
+ payload["channel"] = channel_id
591
+ payload["exp"] = (
592
+ int(self._time_service.timestamp() if self._time_service else datetime.now(timezone.utc).timestamp())
593
+ + ttl
594
+ )
595
+
596
+ return jwt.encode(payload, self.gateway_secret, algorithm="HS256", headers={"kid": wa.jwt_kid})
597
+
598
+ def create_gateway_token(self, wa: WACertificate, expires_hours: int = 8) -> str:
599
+ """Create gateway-signed token (OAuth/password auth)."""
600
+ now = int(self._time_service.timestamp() if self._time_service else datetime.now(timezone.utc).timestamp())
601
+
602
+ payload = {
603
+ "sub": wa.wa_id,
604
+ "sub_type": JWTSubType.OAUTH.value if wa.oauth_provider else JWTSubType.USER.value,
605
+ "name": wa.name,
606
+ "scope": wa.scopes,
607
+ "iat": now,
608
+ "exp": now + (expires_hours * 3600),
609
+ }
610
+
611
+ if wa.oauth_provider:
612
+ payload["oauth_provider"] = wa.oauth_provider
613
+
614
+ return jwt.encode(payload, self.gateway_secret, algorithm="HS256", headers={"kid": wa.jwt_kid})
615
+
616
+ def _create_authority_token(self, wa: WACertificate, private_key: bytes) -> str:
617
+ """Create WA-signed authority token."""
618
+ now = int(self._time_service.timestamp() if self._time_service else datetime.now(timezone.utc).timestamp())
619
+
620
+ payload = {
621
+ "sub": wa.wa_id,
622
+ "sub_type": JWTSubType.AUTHORITY.value,
623
+ "name": wa.name,
624
+ "scope": wa.scopes,
625
+ "iat": now,
626
+ "exp": now + (24 * 3600), # 24 hours
627
+ }
628
+
629
+ # Load Ed25519 private key
630
+ signing_key = ed25519.Ed25519PrivateKey.from_private_bytes(private_key)
631
+
632
+ return jwt.encode(payload, signing_key, algorithm="EdDSA", headers={"kid": wa.jwt_kid})
633
+
634
+ async def _verify_jwt_and_get_context(
635
+ self, token: str
636
+ ) -> Optional[Tuple[AuthorizationContext, Optional[datetime]]]:
637
+ """Verify any JWT token and return auth context and expiration (internal method)."""
638
+ try:
639
+ logger.debug(f"[TOKEN_VERIFY] Starting verification for token (first 20 chars): {token[:20]}...")
640
+
641
+ # Decode header to get kid
642
+ header = jwt.get_unverified_header(token)
643
+ kid = header.get("kid")
644
+ logger.debug(f"[TOKEN_VERIFY] Extracted kid from header: {kid}")
645
+
646
+ if not kid:
647
+ logger.warning("[TOKEN_VERIFY] No kid found in token header")
648
+ return None
649
+
650
+ # Get WA by kid
651
+ wa = await self._get_wa_by_kid(kid)
652
+ logger.debug(f"[TOKEN_VERIFY] WA lookup by kid result: {wa is not None}, wa_id={wa.wa_id if wa else None}")
653
+ if not wa:
654
+ logger.warning(f"[TOKEN_VERIFY] No WA found for kid: {kid}")
655
+ return None
656
+
657
+ # Try to verify with different keys/algorithms based on the issuer (kid)
658
+ decoded = None
659
+
660
+ logger.debug("[TOKEN_VERIFY] Attempting gateway-signed token verification (HS256)")
661
+ # First try gateway-signed tokens (most common)
662
+ try:
663
+ decoded = jwt.decode(token, self.gateway_secret, algorithms=["HS256"])
664
+ logger.debug("[TOKEN_VERIFY] Gateway verification succeeded")
665
+ except jwt.InvalidTokenError as e:
666
+ logger.debug(f"[TOKEN_VERIFY] Gateway verification failed: {type(e).__name__}")
667
+
668
+ # If gateway verification failed, try WA-signed tokens
669
+ if not decoded:
670
+ logger.debug("[TOKEN_VERIFY] Attempting WA-signed token verification (EdDSA)")
671
+ try:
672
+ public_key_bytes = self._decode_public_key(wa.pubkey)
673
+ public_key = ed25519.Ed25519PublicKey.from_public_bytes(public_key_bytes)
674
+ decoded = jwt.decode(token, public_key, algorithms=["EdDSA"])
675
+ logger.debug("[TOKEN_VERIFY] WA verification succeeded")
676
+ except jwt.InvalidTokenError as e:
677
+ logger.info(f"[TOKEN_VERIFY] WA verification failed: {type(e).__name__}")
678
+
679
+ # If no verification succeeded, token is invalid
680
+ if not decoded:
681
+ logger.warning("[TOKEN_VERIFY] Both gateway and WA verification failed - token invalid")
682
+ return None
683
+
684
+ # Validate sub_type and algorithm after verification
685
+ # IMPORTANT: We must validate that the token was verified with the expected algorithm
686
+ # to prevent algorithm confusion attacks
687
+ sub_type = decoded.get("sub_type")
688
+
689
+ # Determine which verification succeeded based on the algorithm
690
+ verified_with_gateway = False
691
+ verified_with_wa_key = False
692
+
693
+ # Re-verify to determine which key actually verified the token
694
+ try:
695
+ jwt.decode(token, self.gateway_secret, algorithms=["HS256"])
696
+ verified_with_gateway = True
697
+ except jwt.InvalidTokenError:
698
+ pass
699
+
700
+ if not verified_with_gateway:
701
+ try:
702
+ public_key_bytes = self._decode_public_key(wa.pubkey)
703
+ public_key = ed25519.Ed25519PublicKey.from_public_bytes(public_key_bytes)
704
+ jwt.decode(token, public_key, algorithms=["EdDSA"])
705
+ verified_with_wa_key = True
706
+ except jwt.InvalidTokenError:
707
+ pass
708
+
709
+ # Validate that the token type matches the verification method
710
+ if sub_type == JWTSubType.AUTHORITY.value:
711
+ # Authority tokens must be verified with WA key (EdDSA)
712
+ if not verified_with_wa_key:
713
+ return None
714
+ elif sub_type in [JWTSubType.ANON.value, JWTSubType.OAUTH.value, JWTSubType.USER.value]:
715
+ # Gateway tokens must be verified with gateway secret (HS256)
716
+ if not verified_with_gateway:
717
+ return None
718
+ else:
719
+ return None
720
+
721
+ # Create authorization context
722
+ # Determine TokenType based on the WA certificate
723
+ if wa.adapter_id:
724
+ token_type = TokenType.CHANNEL
725
+ elif wa.oauth_provider:
726
+ token_type = TokenType.OAUTH
727
+ else:
728
+ token_type = TokenType.STANDARD
729
+
730
+ context = AuthorizationContext(
731
+ wa_id=decoded["sub"],
732
+ role=wa.role,
733
+ token_type=token_type,
734
+ sub_type=JWTSubType(decoded["sub_type"]),
735
+ scopes=decoded["scope"],
736
+ channel_id=decoded.get("channel"),
737
+ )
738
+
739
+ # Update last login
740
+ await self.update_last_login(wa.wa_id)
741
+
742
+ # Extract expiration if present
743
+ exp_timestamp = decoded.get("exp")
744
+ expiration = None
745
+ if exp_timestamp:
746
+ expiration = datetime.fromtimestamp(exp_timestamp, tz=timezone.utc)
747
+
748
+ return (context, expiration)
749
+
750
+ except jwt.InvalidTokenError as e:
751
+ logger.debug(f"[TOKEN_VERIFY] JWT InvalidTokenError: {type(e).__name__}: {str(e)}")
752
+ return None
753
+ except Exception as e:
754
+ logger.debug(f"[TOKEN_VERIFY] Unexpected exception: {type(e).__name__}: {str(e)}", exc_info=True)
755
+ return None
756
+
757
+ # WACrypto Protocol Implementation
758
+
759
+ def generate_keypair(self) -> Tuple[bytes, bytes]:
760
+ """Generate Ed25519 keypair (private, public)."""
761
+ private_key = ed25519.Ed25519PrivateKey.generate()
762
+ public_key = private_key.public_key()
763
+
764
+ private_bytes = private_key.private_bytes(
765
+ encoding=serialization.Encoding.Raw,
766
+ format=serialization.PrivateFormat.Raw,
767
+ encryption_algorithm=serialization.NoEncryption(),
768
+ )
769
+
770
+ public_bytes = public_key.public_bytes(
771
+ encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw
772
+ )
773
+
774
+ return private_bytes, public_bytes
775
+
776
+ def sign_data(self, data: bytes, private_key: bytes) -> str:
777
+ """Sign data with Ed25519 private key."""
778
+ signing_key = ed25519.Ed25519PrivateKey.from_private_bytes(private_key)
779
+ signature = signing_key.sign(data)
780
+ return base64.b64encode(signature).decode()
781
+
782
+ def _verify_signature(self, data: bytes, signature: str, public_key: str) -> bool:
783
+ """Verify Ed25519 signature."""
784
+ try:
785
+ public_key_bytes = self._decode_public_key(public_key)
786
+ verify_key = ed25519.Ed25519PublicKey.from_public_bytes(public_key_bytes)
787
+ signature_bytes = base64.b64decode(signature)
788
+
789
+ verify_key.verify(signature_bytes, data)
790
+ return True
791
+ except (InvalidSignature, Exception):
792
+ return False
793
+
794
+ def hash_password(self, password: str) -> str:
795
+ """Hash password using PBKDF2."""
796
+ salt = secrets.token_bytes(32)
797
+ kdf = PBKDF2HMAC(
798
+ algorithm=hashes.SHA256(),
799
+ length=32,
800
+ salt=salt,
801
+ iterations=100000,
802
+ )
803
+ key = kdf.derive(password.encode())
804
+ return base64.b64encode(salt + key).decode()
805
+
806
+ def _verify_password(self, password: str, hash: str) -> bool:
807
+ """Verify password against hash."""
808
+ try:
809
+ decoded = base64.b64decode(hash)
810
+ salt = decoded[:32]
811
+ stored_key = decoded[32:]
812
+
813
+ kdf = PBKDF2HMAC(
814
+ algorithm=hashes.SHA256(),
815
+ length=32,
816
+ salt=salt,
817
+ iterations=100000,
818
+ )
819
+ key = kdf.derive(password.encode())
820
+ # Use constant-time comparison to prevent timing attacks
821
+ import hmac
822
+
823
+ return hmac.compare_digest(key, stored_key)
824
+ except Exception:
825
+ return False
826
+
827
+ # codeql[py/weak-sensitive-data-hashing]
828
+ # The use of SHA256 here is for generating a unique API key, not for hashing passwords.
829
+ # It is combined with a cryptographically secure random salt, which makes it resistant
830
+ # to rainbow table attacks. For this purpose, SHA256 is considered a secure choice.
831
+ def _generate_api_key(self, wa_id: str) -> str:
832
+ """Generate API key for WA."""
833
+ # Include wa_id in key derivation for uniqueness
834
+ key_material = f"{wa_id}:{secrets.token_hex(32)}"
835
+ return hashlib.sha256(key_material.encode()).hexdigest()
836
+
837
+ def _generate_wa_id(self, timestamp: datetime) -> str:
838
+ """Generate a unique WA (Wise Authority) ID.
839
+
840
+ Format: wa-YYYY-MM-DD-XXXXXX
841
+ - wa: Fixed prefix for all WA IDs
842
+ - YYYY-MM-DD: Date from the provided timestamp
843
+ - XXXXXX: 6 uppercase hexadecimal characters (cryptographically random)
844
+
845
+ Args:
846
+ timestamp: The timestamp to use for the date portion
847
+
848
+ Returns:
849
+ A unique WA ID string
850
+
851
+ Example:
852
+ wa-2025-07-14-A3F2B1
853
+ """
854
+ date_str = timestamp.strftime("%Y-%m-%d")
855
+ # Generate 3 random bytes = 6 hex characters
856
+ random_suffix = secrets.token_hex(3).upper()
857
+ return f"wa-{date_str}-{random_suffix}"
858
+
859
+ # AuthenticationServiceProtocol Implementation
860
+
861
+ async def authenticate(self, token: str) -> Optional[AuthenticationResult]:
862
+ """Authenticate a WA token and return identity info."""
863
+ self._auth_attempts += 1
864
+ try:
865
+ claims = await self.verify_token(token)
866
+ if not claims:
867
+ self._auth_failures += 1
868
+ return None
869
+
870
+ if hasattr(claims, "get"):
871
+ wa_id = claims.get("wa_id")
872
+ else:
873
+ wa_id = getattr(claims, "wa_id", None)
874
+ if not wa_id:
875
+ self._auth_failures += 1
876
+ return None
877
+
878
+ # Update last login
879
+ await self.update_last_login(wa_id)
880
+
881
+ # Get WA details
882
+ wa = await self.get_wa(wa_id)
883
+ if not wa:
884
+ self._auth_failures += 1
885
+ return None
886
+
887
+ self._auth_successes += 1
888
+ self._session_count += 1 # Track active session
889
+ return AuthenticationResult(
890
+ authenticated=True,
891
+ wa_id=wa_id,
892
+ name=wa.name,
893
+ role=wa.role.value,
894
+ expires_at=(
895
+ datetime.fromtimestamp(claims.get("exp", 0), tz=timezone.utc)
896
+ if hasattr(claims, "get")
897
+ else (self._time_service.now() if self._time_service else datetime.now(timezone.utc))
898
+ ),
899
+ permissions=wa.scopes,
900
+ metadata={},
901
+ )
902
+ except Exception as e:
903
+ logger.error(f"Authentication failed: {e}")
904
+ self._auth_failures += 1
905
+ return None
906
+
907
+ async def create_token(self, wa_id: str, token_type: TokenType, ttl: int = 3600) -> str:
908
+ """Create a new authentication token."""
909
+ wa = await self.get_wa(wa_id)
910
+ if not wa:
911
+ raise ValueError(f"WA {wa_id} not found")
912
+
913
+ # Map TokenType enum to our token creation methods
914
+ if token_type == TokenType.CHANNEL:
915
+ # CHANNEL tokens require a channel_id, use create_channel_token directly
916
+ raise ValueError("CHANNEL tokens require channel_id - use create_channel_token directly")
917
+ elif token_type == TokenType.STANDARD:
918
+ return self.create_gateway_token(wa, expires_hours=ttl // 3600)
919
+ else:
920
+ raise ValueError(f"Unsupported token type: {token_type}")
921
+
922
+ async def verify_token(self, token: str) -> Optional[TokenVerification]:
923
+ """Verify and decode a token (AuthenticationServiceProtocol version)."""
924
+ try:
925
+ # Directly call _verify_jwt_and_get_context to get both context and expiration
926
+ result = await self._verify_jwt_and_get_context(token)
927
+ if not result:
928
+ return None
929
+
930
+ context, expiration = result
931
+
932
+ # Get the WA name
933
+ wa = await self.get_wa(context.wa_id)
934
+ wa_name = wa.name if wa else context.wa_id
935
+
936
+ # Use expiration from token, or current time as fallback
937
+ expires_at = (
938
+ expiration
939
+ if expiration
940
+ else (self._time_service.now() if self._time_service else datetime.now(timezone.utc))
941
+ )
942
+
943
+ return TokenVerification(
944
+ valid=True,
945
+ wa_id=context.wa_id,
946
+ name=wa_name,
947
+ role=context.role.value,
948
+ expires_at=expires_at,
949
+ error=None,
950
+ )
951
+ except Exception as e:
952
+ logger.error(f"Token verification failed: {e}")
953
+ return TokenVerification(valid=False, wa_id=None, name=None, role=None, expires_at=None, error=str(e))
954
+
955
+ async def create_wa(
956
+ self, name: str, email: str, scopes: List[str], role: WARole = WARole.OBSERVER
957
+ ) -> WACertificate:
958
+ """Create a new Wise Authority identity."""
959
+ # Generate keypair
960
+ private_key, public_key = self.generate_keypair()
961
+
962
+ # Create certificate
963
+ timestamp = self._time_service.now() if self._time_service else datetime.now(timezone.utc)
964
+ wa_id = self._generate_wa_id(timestamp)
965
+ jwt_kid = f"wa-jwt-{wa_id[-6:].lower()}"
966
+
967
+ wa_cert = WACertificate(
968
+ wa_id=wa_id,
969
+ name=name,
970
+ role=role,
971
+ pubkey=self._encode_public_key(public_key),
972
+ jwt_kid=jwt_kid,
973
+ scopes_json=json.dumps(scopes),
974
+ created_at=timestamp,
975
+ )
976
+
977
+ # Store in database
978
+ await self._store_wa_certificate(wa_cert)
979
+
980
+ # Store private key (in production, this would be in a secure key store)
981
+ # For now, we're not storing it as it's managed externally
982
+
983
+ # Add audit log entry for WA creation (mint)
984
+ if hasattr(self, "_audit_service") and self._audit_service:
985
+ from ciris_engine.schemas.audit.core import EventPayload
986
+
987
+ event_data = EventPayload(
988
+ action="wa_mint",
989
+ service_name="authentication",
990
+ user_id=wa_id, # Use wa_id as user_id to track which WA was created
991
+ result="success",
992
+ )
993
+ await self._audit_service.log_event(event_type="wa_mint", event_data=event_data)
994
+
995
+ return wa_cert
996
+
997
+ async def list_was(self, active_only: bool = True) -> List[WACertificate]:
998
+ """List Wise Authority identities."""
999
+ return await self._list_all_was(active_only=active_only)
1000
+
1001
+ async def rotate_keys(self, wa_id: str) -> bool:
1002
+ """Rotate cryptographic keys for a WA."""
1003
+ wa = await self.get_wa(wa_id)
1004
+ if not wa:
1005
+ return False
1006
+
1007
+ # Generate new keypair
1008
+ private_key, public_key = self.generate_keypair()
1009
+
1010
+ # Update WA with new public key
1011
+ wa.pubkey = self._encode_public_key(public_key)
1012
+
1013
+ # Store the updated certificate
1014
+ await self.update_wa(wa_id, pubkey=wa.pubkey)
1015
+
1016
+ logger.info(f"Rotated keys for WA {wa_id}")
1017
+ return True
1018
+
1019
+ # Original verify_token implementation (renamed for internal use)
1020
+ async def _verify_token_internal(self, token: Optional[str]) -> Optional[AuthorizationContext]:
1021
+ """Authenticate request and return auth context."""
1022
+ if not token:
1023
+ return None
1024
+
1025
+ # Check cache first
1026
+ if token in self._token_cache:
1027
+ return self._token_cache[token]
1028
+
1029
+ # Verify token
1030
+ result = await self._verify_jwt_and_get_context(token)
1031
+
1032
+ if result:
1033
+ context, _ = result # We don't need expiration here
1034
+ # Cache valid tokens
1035
+ self._token_cache[token] = context
1036
+ return context
1037
+
1038
+ return None
1039
+
1040
+ def _require_scope(self, scope: str) -> Callable[[F], F]:
1041
+ """Decorator to require specific scope for endpoint."""
1042
+
1043
+ def decorator(func: F) -> F:
1044
+ async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
1045
+ # Extract auth context from kwargs
1046
+ auth_context = kwargs.get("auth_context")
1047
+
1048
+ if not auth_context:
1049
+ # Try to get token and verify it
1050
+ token = kwargs.get("token")
1051
+ if token:
1052
+ auth_context = await self._verify_token_internal(token)
1053
+
1054
+ if not auth_context:
1055
+ raise ValueError(f"Authentication required for scope '{scope}'")
1056
+
1057
+ # Check if the auth context has the required scope
1058
+ if not hasattr(auth_context, "scopes") or scope not in auth_context.scopes:
1059
+ raise ValueError(
1060
+ f"Insufficient permissions: Requires scope '{scope}', "
1061
+ f"but user has scopes: {getattr(auth_context, 'scopes', [])}"
1062
+ )
1063
+
1064
+ # Add auth context to kwargs if not already present
1065
+ if "auth_context" not in kwargs:
1066
+ kwargs["auth_context"] = auth_context
1067
+
1068
+ return await func(*args, **kwargs)
1069
+
1070
+ def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
1071
+ # Extract auth context from kwargs
1072
+ auth_context = kwargs.get("auth_context")
1073
+
1074
+ if not auth_context:
1075
+ raise ValueError(f"Authentication required for scope '{scope}'")
1076
+
1077
+ # Check if the auth context has the required scope
1078
+ if not hasattr(auth_context, "scopes") or scope not in auth_context.scopes:
1079
+ raise ValueError(
1080
+ f"Insufficient permissions: Requires scope '{scope}', "
1081
+ f"but user has scopes: {getattr(auth_context, 'scopes', [])}"
1082
+ )
1083
+
1084
+ return func(*args, **kwargs)
1085
+
1086
+ # Preserve function metadata and check if the function is async
1087
+ if asyncio.iscoroutinefunction(func):
1088
+ wrapper = functools.wraps(func)(async_wrapper)
1089
+ else:
1090
+ wrapper = functools.wraps(func)(sync_wrapper)
1091
+
1092
+ # Add metadata to indicate this function requires authentication
1093
+ setattr(wrapper, "_requires_scope", scope)
1094
+
1095
+ return wrapper # type: ignore
1096
+
1097
+ return decorator
1098
+
1099
+ def _require_wa_auth(self, scope: str) -> Callable[[F], F]:
1100
+ """Decorator to require WA authentication with specific scope.
1101
+
1102
+ This decorator checks for authentication tokens in the following order:
1103
+ 1. 'token' parameter in the function arguments
1104
+ 2. 'auth_context' in the function arguments
1105
+ 3. Token from the context (if available)
1106
+
1107
+ Args:
1108
+ scope: The required scope for accessing the decorated function
1109
+
1110
+ Returns:
1111
+ Decorated function that enforces authentication
1112
+ """
1113
+
1114
+ def decorator(func: F) -> F:
1115
+ async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
1116
+ # Extract token from various sources
1117
+ token = None
1118
+ auth_context = None
1119
+
1120
+ # Check if 'token' is in kwargs
1121
+ if "token" in kwargs:
1122
+ token = kwargs.get("token")
1123
+
1124
+ # Check if 'auth_context' is already provided
1125
+ if "auth_context" in kwargs:
1126
+ auth_context = kwargs.get("auth_context")
1127
+
1128
+ # If no auth context yet, try to verify the token
1129
+ if not auth_context and token:
1130
+ auth_context = await self.verify_token(token)
1131
+
1132
+ # Check if authentication succeeded
1133
+ if not auth_context:
1134
+ raise ValueError("Authentication required: No valid token provided")
1135
+
1136
+ # Verify the required scope
1137
+ if not auth_context.has_scope(scope):
1138
+ raise ValueError(
1139
+ f"Insufficient permissions: Requires scope '{scope}', "
1140
+ f"but user has scopes: {auth_context.scopes}"
1141
+ )
1142
+
1143
+ # Check if the function accepts auth_context parameter
1144
+ sig = inspect.signature(func)
1145
+
1146
+ # If function has **kwargs or auth_context parameter, pass it
1147
+ if "auth_context" in sig.parameters or any(
1148
+ p.kind == inspect.Parameter.VAR_KEYWORD for p in sig.parameters.values()
1149
+ ):
1150
+ kwargs["auth_context"] = auth_context
1151
+
1152
+ # Call the original function
1153
+ return await func(*args, **kwargs)
1154
+
1155
+ def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
1156
+ # For synchronous functions, we need to handle auth differently
1157
+ # This is a simplified version that expects auth_context to be pre-verified
1158
+ auth_context = kwargs.get("auth_context")
1159
+
1160
+ if not auth_context:
1161
+ raise ValueError("Authentication required: No auth_context provided")
1162
+
1163
+ # Verify the required scope
1164
+ if not hasattr(auth_context, "has_scope") or not auth_context.has_scope(scope):
1165
+ raise ValueError(f"Insufficient permissions: Requires scope '{scope}'")
1166
+
1167
+ return func(*args, **kwargs)
1168
+
1169
+ # Preserve function metadata and check if the function is async
1170
+ if asyncio.iscoroutinefunction(func):
1171
+ wrapper = functools.wraps(func)(async_wrapper)
1172
+ else:
1173
+ wrapper = functools.wraps(func)(sync_wrapper)
1174
+
1175
+ # Add metadata to indicate this function requires authentication
1176
+ setattr(wrapper, "_requires_wa_auth", True)
1177
+ setattr(wrapper, "_required_scope", scope)
1178
+
1179
+ return wrapper # type: ignore
1180
+
1181
+ return decorator
1182
+
1183
+ def _get_adapter_token(self, adapter_id: str) -> Optional[str]:
1184
+ """Get cached adapter token."""
1185
+ return self._channel_token_cache.get(adapter_id)
1186
+
1187
+ # Additional helper methods
1188
+
1189
+ async def _get_system_wa(self) -> Optional[WACertificate]:
1190
+ """Get the system WA certificate if it exists."""
1191
+ was = await self._list_all_was()
1192
+ for wa in was:
1193
+ if wa.role == WARole.AUTHORITY and wa.name == "CIRIS System Authority":
1194
+ return wa
1195
+ return None
1196
+
1197
+ async def get_system_wa_id(self) -> Optional[str]:
1198
+ """Get the system WA ID for signing system tasks."""
1199
+ system_wa = await self._get_system_wa()
1200
+ return system_wa.wa_id if system_wa else None
1201
+
1202
+ async def ensure_system_wa_exists(self) -> Optional[str]:
1203
+ """Ensure the system WA exists, creating it if a ROOT WA is available.
1204
+
1205
+ This should be called after creating a ROOT WA during setup to ensure
1206
+ the system WA is immediately available for signing system tasks.
1207
+
1208
+ Returns:
1209
+ The system WA ID if it exists or was created, None if no ROOT WA exists.
1210
+ """
1211
+ # Check if system WA already exists
1212
+ system_wa = await self._get_system_wa()
1213
+ if system_wa:
1214
+ return system_wa.wa_id
1215
+
1216
+ # Find a ROOT WA to use as parent
1217
+ for wa in await self._list_all_was():
1218
+ if wa.role == WARole.ROOT:
1219
+ # Create system WA as child of root
1220
+ new_system_wa = await self._create_system_wa_certificate(wa.wa_id)
1221
+ logger.info(f"✅ Created system WA {new_system_wa.wa_id} as child of ROOT {wa.wa_id}")
1222
+ return new_system_wa.wa_id
1223
+
1224
+ logger.warning("Cannot create system WA - no ROOT WA found")
1225
+ return None
1226
+
1227
+ async def _create_system_wa_certificate(self, parent_wa_id: str) -> WACertificate:
1228
+ """Create the system WA certificate as a child of the root certificate.
1229
+
1230
+ This certificate is used to sign system-generated tasks like WAKEUP and DREAM.
1231
+ It respects the authority of the root certificate holder.
1232
+ """
1233
+ # Generate keypair for system WA
1234
+ private_key, public_key = self.generate_keypair()
1235
+
1236
+ # Store the private key securely
1237
+ system_key_path = self.key_dir / "system_wa.key"
1238
+ system_key_path.write_bytes(private_key)
1239
+ system_key_path.chmod(0o600)
1240
+
1241
+ # Create the system WA certificate
1242
+ timestamp = self._time_service.now() if self._time_service else datetime.now(timezone.utc)
1243
+ wa_id = self._generate_wa_id(timestamp)
1244
+ jwt_kid = f"wa-jwt-{wa_id[-6:].lower()}"
1245
+
1246
+ # Create data to sign - the certificate attributes
1247
+ cert_data = {
1248
+ "wa_id": wa_id,
1249
+ "name": "CIRIS System Authority",
1250
+ "role": WARole.AUTHORITY.value,
1251
+ "pubkey": self._encode_public_key(public_key),
1252
+ "parent_wa_id": parent_wa_id,
1253
+ "created_at": timestamp.isoformat(),
1254
+ }
1255
+
1256
+ # Sign with the system's private key (self-signed for now)
1257
+ # In production, this would be signed by the parent's private key
1258
+ signature_data = json.dumps(cert_data, sort_keys=True, separators=(",", ":"))
1259
+ parent_signature = self.sign_data(signature_data.encode("utf-8"), private_key)
1260
+
1261
+ # Create the certificate
1262
+ system_wa = WACertificate(
1263
+ wa_id=wa_id,
1264
+ name="CIRIS System Authority",
1265
+ role=WARole.AUTHORITY,
1266
+ pubkey=self._encode_public_key(public_key),
1267
+ jwt_kid=jwt_kid,
1268
+ parent_wa_id=parent_wa_id,
1269
+ parent_signature=parent_signature,
1270
+ scopes_json=json.dumps(
1271
+ [
1272
+ "system.task.create",
1273
+ "system.task.sign",
1274
+ "system.wakeup",
1275
+ "system.dream",
1276
+ "system.shutdown",
1277
+ "memory.read",
1278
+ "memory.write",
1279
+ ]
1280
+ ),
1281
+ created_at=timestamp,
1282
+ )
1283
+
1284
+ # Store in database
1285
+ await self._store_wa_certificate(system_wa)
1286
+ logger.info(f"Created system WA certificate: {wa_id} (child of {parent_wa_id})")
1287
+
1288
+ return system_wa
1289
+
1290
+ async def sign_task(self, task: "Task", wa_id: str) -> Tuple[str, str]:
1291
+ """Sign a task with a WA's private key.
1292
+
1293
+ Returns:
1294
+ Tuple of (signature, signed_at timestamp)
1295
+ """
1296
+ # Get the WA certificate
1297
+ wa = await self.get_wa(wa_id)
1298
+ if not wa:
1299
+ raise ValueError(f"WA {wa_id} not found")
1300
+
1301
+ # Load the private key
1302
+ if wa.name == "CIRIS System Authority":
1303
+ # System WA key is stored locally
1304
+ key_path = self.key_dir / "system_wa.key"
1305
+ if not key_path.exists():
1306
+ raise ValueError("System WA private key not found")
1307
+ private_key = key_path.read_bytes()
1308
+ else:
1309
+ # Other WAs would have their keys managed differently
1310
+ raise ValueError(f"Private key management not implemented for WA {wa_id}")
1311
+
1312
+ # Create canonical representation of task for signing
1313
+ task_data = {
1314
+ "task_id": task.task_id,
1315
+ "description": task.description,
1316
+ "status": task.status.value if hasattr(task.status, "value") else str(task.status),
1317
+ "priority": task.priority,
1318
+ "created_at": task.created_at,
1319
+ "parent_task_id": task.parent_task_id,
1320
+ "context": task.context.model_dump() if task.context else None,
1321
+ }
1322
+
1323
+ canonical_json = json.dumps(task_data, sort_keys=True, separators=(",", ":"))
1324
+ signature = self.sign_data(canonical_json.encode("utf-8"), private_key)
1325
+ signed_at = (self._time_service.now() if self._time_service else datetime.now(timezone.utc)).isoformat()
1326
+
1327
+ return signature, signed_at
1328
+
1329
+ async def verify_task_signature(self, task: "Task") -> bool:
1330
+ """Verify a task's signature.
1331
+
1332
+ Returns:
1333
+ True if signature is valid, False otherwise
1334
+ """
1335
+ if not task.signed_by or not task.signature or not task.signed_at:
1336
+ return False
1337
+
1338
+ # Get the WA that signed it
1339
+ wa = await self.get_wa(task.signed_by)
1340
+ if not wa:
1341
+ return False
1342
+
1343
+ # Recreate the canonical representation
1344
+ task_data = {
1345
+ "task_id": task.task_id,
1346
+ "description": task.description,
1347
+ "status": task.status.value if hasattr(task.status, "value") else str(task.status),
1348
+ "priority": task.priority,
1349
+ "created_at": task.created_at,
1350
+ "parent_task_id": task.parent_task_id,
1351
+ "context": task.context.model_dump() if task.context else None,
1352
+ }
1353
+
1354
+ canonical_json = json.dumps(task_data, sort_keys=True, separators=(",", ":"))
1355
+
1356
+ # Verify the signature
1357
+ return self._verify_signature(canonical_json.encode("utf-8"), task.signature, wa.pubkey)
1358
+
1359
+ async def bootstrap_if_needed(self) -> None:
1360
+ """Bootstrap the system if no WAs exist."""
1361
+ was = await self._list_all_was()
1362
+
1363
+ # Check if we have a root WA
1364
+ has_root = any(wa.role == WARole.ROOT for wa in was)
1365
+
1366
+ if not has_root:
1367
+ # Load and insert root certificate
1368
+ seed_path = Path(__file__).parent.parent.parent / "seed" / "root_pub.json"
1369
+ if seed_path.exists():
1370
+ async with aiofiles.open(seed_path) as f:
1371
+ content = await f.read()
1372
+ root_data = json.loads(content)
1373
+
1374
+ # Convert created timestamp - handle both 'Z' and '+00:00' formats
1375
+ created_str = root_data["created"]
1376
+ if created_str.endswith("Z"):
1377
+ created_str = created_str[:-1] + "+00:00"
1378
+ root_data["created"] = datetime.fromisoformat(created_str)
1379
+
1380
+ root_wa = WACertificate(**root_data)
1381
+ await self._store_wa_certificate(root_wa)
1382
+
1383
+ logger.info(f"Loaded root WA certificate: {root_wa.wa_id}")
1384
+
1385
+ # Check if system WA exists, create if not (whether we just loaded root or not)
1386
+ system_wa = await self._get_system_wa()
1387
+ if not system_wa:
1388
+ # Find the root certificate
1389
+ found_root_wa: Optional[WACertificate] = None
1390
+ for wa in await self._list_all_was():
1391
+ if wa.role == WARole.ROOT:
1392
+ found_root_wa = wa
1393
+ break
1394
+
1395
+ if found_root_wa:
1396
+ # Create system WA certificate as child of root
1397
+ await self._create_system_wa_certificate(found_root_wa.wa_id)
1398
+ else:
1399
+ logger.warning("No root WA certificate found - cannot create system WA")
1400
+
1401
+ async def _create_channel_token_for_adapter(self, adapter_type: str, adapter_info: JSONDict) -> str:
1402
+ """Create a channel token for an adapter."""
1403
+ # Ensure adapter_info has proper structure
1404
+ if not adapter_info:
1405
+ adapter_info = {}
1406
+
1407
+ # Add default instance_id if not present
1408
+ if "instance_id" not in adapter_info:
1409
+ adapter_info["instance_id"] = "default"
1410
+
1411
+ # Create channel identity from adapter info
1412
+ channel_identity = ChannelIdentity(
1413
+ adapter_type=adapter_type,
1414
+ adapter_instance_id=adapter_info.get("instance_id", "default"),
1415
+ external_user_id=adapter_info.get("user_id", "system"),
1416
+ external_username=adapter_info.get("username", adapter_type),
1417
+ metadata=adapter_info,
1418
+ )
1419
+
1420
+ # Create or get adapter observer
1421
+ adapter_id = f"{adapter_type}_{channel_identity.adapter_instance_id}"
1422
+ observer = await self._create_adapter_observer(adapter_id, f"{adapter_type}_observer")
1423
+
1424
+ # Generate token - for observers, channel_id is not used
1425
+ token = await self.create_channel_token(observer.wa_id, adapter_id, ttl=0) # ttl=0 means no expiry
1426
+
1427
+ # Cache the token
1428
+ self._channel_token_cache[adapter_id] = token
1429
+
1430
+ return token
1431
+
1432
+ def verify_token_sync(self, token: str) -> Optional[JSONDict]:
1433
+ """Synchronously verify a token (for non-async contexts)."""
1434
+ try:
1435
+ # For sync verification, we can only verify gateway-signed tokens
1436
+ # since authority tokens require async DB lookups for public keys
1437
+
1438
+ # Try gateway-signed token verification
1439
+ try:
1440
+ # Verify the token with gateway secret first
1441
+ decoded = jwt.decode(token, self.gateway_secret, algorithms=["HS256"])
1442
+
1443
+ # Now that the token is verified, we can trust its contents
1444
+ # Validate that this is indeed a gateway-signed token type
1445
+ sub_type = decoded.get("sub_type")
1446
+ if sub_type in [JWTSubType.ANON.value, JWTSubType.OAUTH.value, JWTSubType.USER.value]:
1447
+ # Valid gateway token
1448
+ return dict(decoded)
1449
+ else:
1450
+ # Invalid sub_type for gateway token
1451
+ return None
1452
+
1453
+ except jwt.InvalidTokenError:
1454
+ # Token failed verification with gateway secret
1455
+ pass
1456
+
1457
+ # Authority tokens require async DB access for public key retrieval
1458
+ # So we cannot verify them in sync mode
1459
+ return None
1460
+
1461
+ except Exception:
1462
+ return None
1463
+
1464
+ def get_capabilities(self) -> ServiceCapabilities:
1465
+ """Get service capabilities."""
1466
+ from uuid import uuid4
1467
+
1468
+ from ciris_engine.schemas.services.core import ServiceCapabilities
1469
+ from ciris_engine.schemas.services.metadata import ServiceMetadata
1470
+
1471
+ return ServiceCapabilities(
1472
+ service_name="AuthenticationService",
1473
+ actions=[
1474
+ "authenticate",
1475
+ "create_token",
1476
+ "verify_token",
1477
+ "create_wa",
1478
+ "revoke_wa",
1479
+ "update_wa",
1480
+ "list_was",
1481
+ "get_wa",
1482
+ "rotate_keys",
1483
+ "bootstrap_if_needed",
1484
+ "create_channel_token",
1485
+ "verify_token_sync",
1486
+ "update_last_login",
1487
+ ],
1488
+ version="1.0.0",
1489
+ dependencies=["TimeService"],
1490
+ metadata=ServiceMetadata(
1491
+ category="infrastructure",
1492
+ critical=True,
1493
+ description="Infrastructure service for WA authentication and identity management",
1494
+ ),
1495
+ )
1496
+
1497
+ def get_status(self) -> ServiceStatus:
1498
+ """Get current service status."""
1499
+ from ciris_engine.schemas.services.core import ServiceStatus
1500
+
1501
+ # Count certificates by type
1502
+ try:
1503
+ counts = authentication_store.get_certificate_counts(self.db_path)
1504
+ cert_count = counts.get("active", 0)
1505
+ revoked_count = counts.get("revoked", 0)
1506
+ # Extract by_role dict with type assertion
1507
+ role_counts_raw: int | Dict[str, int] = counts.get("by_role", {})
1508
+ role_counts: Dict[str, int] = (
1509
+ role_counts_raw
1510
+ if isinstance(role_counts_raw, dict)
1511
+ else {"OBSERVER": 0, "USER": 0, "ADMIN": 0, "AUTHORITY": 0, "ROOT": 0}
1512
+ )
1513
+ except Exception as e:
1514
+ logger.warning(
1515
+ f"Authentication service health check failed: {type(e).__name__}: {str(e)} - Unable to access auth database"
1516
+ )
1517
+ cert_count = 0
1518
+ role_counts = {"OBSERVER": 0, "USER": 0, "ADMIN": 0, "AUTHORITY": 0, "ROOT": 0}
1519
+ revoked_count = 0
1520
+
1521
+ current_time = self._time_service.now() if self._time_service else datetime.now(timezone.utc)
1522
+ uptime_seconds = 0.0
1523
+ if self._start_time:
1524
+ uptime_seconds = (current_time - self._start_time).total_seconds()
1525
+
1526
+ # Calculate token cache stats
1527
+ auth_context_cached = len(self._token_cache)
1528
+ channel_tokens_cached = len(self._channel_token_cache)
1529
+
1530
+ # Build custom metrics
1531
+ custom_metrics = {
1532
+ "active_certificates": float(cert_count),
1533
+ "revoked_certificates": float(revoked_count),
1534
+ "observer_certificates": float(role_counts.get("OBSERVER", 0)),
1535
+ "user_certificates": float(role_counts.get("USER", 0)),
1536
+ "admin_certificates": float(role_counts.get("ADMIN", 0)),
1537
+ "authority_certificates": float(role_counts.get("AUTHORITY", 0)),
1538
+ "root_certificates": float(role_counts.get("ROOT", 0)),
1539
+ "auth_contexts_cached": float(auth_context_cached),
1540
+ "channel_tokens_cached": float(channel_tokens_cached),
1541
+ "total_tokens_cached": float(auth_context_cached + channel_tokens_cached),
1542
+ }
1543
+
1544
+ return ServiceStatus(
1545
+ service_name="AuthenticationService",
1546
+ service_type="infrastructure_service",
1547
+ is_healthy=True, # Simple health check
1548
+ uptime_seconds=uptime_seconds,
1549
+ last_error=None,
1550
+ metrics={
1551
+ "certificate_count": float(cert_count),
1552
+ "cached_tokens": float(len(self._channel_token_cache)),
1553
+ "active_sessions": 0.0,
1554
+ },
1555
+ custom_metrics=custom_metrics,
1556
+ last_health_check=current_time,
1557
+ )
1558
+
1559
+ async def start(self) -> None:
1560
+ """Start the service."""
1561
+ await super().start()
1562
+ self._started = True
1563
+ self._start_time = self._time_service.now() if self._time_service else datetime.now(timezone.utc)
1564
+ logger.info("AuthenticationService started")
1565
+
1566
+ async def stop(self) -> None:
1567
+ """Stop the service."""
1568
+ await super().stop()
1569
+ self._started = False
1570
+ # Clear caches
1571
+ self._token_cache.clear()
1572
+ self._channel_token_cache.clear()
1573
+ logger.info("AuthenticationService stopped")
1574
+
1575
+ def _collect_custom_metrics(self) -> Dict[str, float]:
1576
+ """Collect authentication-specific metrics."""
1577
+ metrics = super()._collect_custom_metrics()
1578
+
1579
+ # Calculate auth success rate
1580
+ auth_rate = 0.0
1581
+ if self._auth_attempts > 0:
1582
+ auth_rate = self._auth_successes / self._auth_attempts
1583
+
1584
+ metrics.update(
1585
+ {
1586
+ "auth_attempts": float(self._auth_attempts),
1587
+ "auth_successes": float(self._auth_successes),
1588
+ "auth_failures": float(self._auth_failures),
1589
+ "auth_success_rate": auth_rate,
1590
+ "token_validations": float(self._token_validations),
1591
+ "permission_checks": float(self._permission_checks),
1592
+ "role_assignments": float(self._role_assignments),
1593
+ "active_sessions": float(self._session_count),
1594
+ "expired_sessions": float(self._expired_sessions),
1595
+ "active_tokens": float(self._active_tokens),
1596
+ }
1597
+ )
1598
+
1599
+ return metrics
1600
+
1601
+ async def get_metrics(self) -> Dict[str, float]:
1602
+ """Get all authentication service metrics including base, custom, and v1.4.3 specific.
1603
+
1604
+ Returns:
1605
+ Dict with all metrics including base, custom, and v1.4.3 metrics
1606
+ """
1607
+ # Get all base + custom metrics
1608
+ metrics = self._collect_metrics()
1609
+
1610
+ current_time = self._time_service.now() if self._time_service else datetime.now(timezone.utc)
1611
+ uptime_seconds = 0.0
1612
+ if self._start_time:
1613
+ uptime_seconds = (current_time - self._start_time).total_seconds()
1614
+
1615
+ # Add v1.4.3 specific metrics
1616
+ metrics.update(
1617
+ {
1618
+ "auth_attempts_total": float(self._auth_attempts),
1619
+ "auth_successes_total": float(self._auth_successes),
1620
+ "auth_failures_total": float(self._auth_failures),
1621
+ "auth_active_sessions": float(self._session_count),
1622
+ "auth_uptime_seconds": uptime_seconds,
1623
+ }
1624
+ )
1625
+
1626
+ return metrics
1627
+
1628
+ async def is_healthy(self) -> bool:
1629
+ """Check if service is healthy."""
1630
+ if not self._started:
1631
+ return False
1632
+
1633
+ # Check database connection via store
1634
+ return authentication_store.check_database_health(self.db_path)