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.
- ciris_adapters/README.md +113 -0
- ciris_adapters/__init__.py +30 -0
- ciris_adapters/ciris_covenant_metrics/README.md +144 -0
- ciris_adapters/ciris_covenant_metrics/__init__.py +36 -0
- ciris_adapters/ciris_covenant_metrics/adapter.py +249 -0
- ciris_adapters/ciris_covenant_metrics/manifest.json +152 -0
- ciris_adapters/ciris_covenant_metrics/services.py +403 -0
- ciris_adapters/ciris_hosted_tools/__init__.py +24 -0
- ciris_adapters/ciris_hosted_tools/adapter.py +169 -0
- ciris_adapters/ciris_hosted_tools/manifest.json +94 -0
- ciris_adapters/ciris_hosted_tools/services.py +744 -0
- ciris_adapters/external_data_sql/README.md +559 -0
- ciris_adapters/external_data_sql/__init__.py +43 -0
- ciris_adapters/external_data_sql/adapter.py +144 -0
- ciris_adapters/external_data_sql/configurable.py +315 -0
- ciris_adapters/external_data_sql/dialects/__init__.py +37 -0
- ciris_adapters/external_data_sql/dialects/base.py +133 -0
- ciris_adapters/external_data_sql/dialects/mysql.py +63 -0
- ciris_adapters/external_data_sql/dialects/postgresql.py +59 -0
- ciris_adapters/external_data_sql/dialects/sqlite.py +62 -0
- ciris_adapters/external_data_sql/example_config.json +88 -0
- ciris_adapters/external_data_sql/example_privacy_schema.yaml +127 -0
- ciris_adapters/external_data_sql/manifest.json +195 -0
- ciris_adapters/external_data_sql/privacy_schema_loader.py +189 -0
- ciris_adapters/external_data_sql/protocol.py +101 -0
- ciris_adapters/external_data_sql/schemas.py +146 -0
- ciris_adapters/external_data_sql/service.py +1547 -0
- ciris_adapters/external_data_sql/service_old.py +492 -0
- ciris_adapters/home_assistant/__init__.py +63 -0
- ciris_adapters/home_assistant/adapter.py +201 -0
- ciris_adapters/home_assistant/communication_service.py +347 -0
- ciris_adapters/home_assistant/configurable.py +667 -0
- ciris_adapters/home_assistant/manifest.json +203 -0
- ciris_adapters/home_assistant/schemas.py +129 -0
- ciris_adapters/home_assistant/service.py +751 -0
- ciris_adapters/home_assistant/tool_service.py +441 -0
- ciris_adapters/mcp_client/__init__.py +82 -0
- ciris_adapters/mcp_client/adapter.py +847 -0
- ciris_adapters/mcp_client/config.py +280 -0
- ciris_adapters/mcp_client/configurable.py +422 -0
- ciris_adapters/mcp_client/manifest.json +185 -0
- ciris_adapters/mcp_client/mcp_communication_service.py +393 -0
- ciris_adapters/mcp_client/mcp_tool_service.py +463 -0
- ciris_adapters/mcp_client/mcp_wise_service.py +394 -0
- ciris_adapters/mcp_client/schemas.py +149 -0
- ciris_adapters/mcp_client/security.py +592 -0
- ciris_adapters/mcp_common/__init__.py +44 -0
- ciris_adapters/mcp_common/manifest.json +25 -0
- ciris_adapters/mcp_common/protocol.py +315 -0
- ciris_adapters/mcp_common/schemas.py +225 -0
- ciris_adapters/mcp_server/__init__.py +47 -0
- ciris_adapters/mcp_server/adapter.py +581 -0
- ciris_adapters/mcp_server/config.py +260 -0
- ciris_adapters/mcp_server/configurable.py +393 -0
- ciris_adapters/mcp_server/handlers.py +663 -0
- ciris_adapters/mcp_server/manifest.json +211 -0
- ciris_adapters/mcp_server/security.py +500 -0
- ciris_adapters/mock_llm/README.md +117 -0
- ciris_adapters/mock_llm/__init__.py +21 -0
- ciris_adapters/mock_llm/adapter.py +131 -0
- ciris_adapters/mock_llm/configurable.py +237 -0
- ciris_adapters/mock_llm/manifest.json +106 -0
- ciris_adapters/mock_llm/protocol.py +37 -0
- ciris_adapters/mock_llm/responses.py +520 -0
- ciris_adapters/mock_llm/responses_action_selection.py +1041 -0
- ciris_adapters/mock_llm/responses_epistemic.py +17 -0
- ciris_adapters/mock_llm/responses_feedback.py +27 -0
- ciris_adapters/mock_llm/schemas.py +35 -0
- ciris_adapters/mock_llm/service.py +294 -0
- ciris_adapters/navigation/__init__.py +21 -0
- ciris_adapters/navigation/adapter.py +129 -0
- ciris_adapters/navigation/configurable.py +239 -0
- ciris_adapters/navigation/manifest.json +104 -0
- ciris_adapters/navigation/service.py +487 -0
- ciris_adapters/reddit/README.md +132 -0
- ciris_adapters/reddit/REDDIT_ADAPTER_ANALYSIS.md +715 -0
- ciris_adapters/reddit/REDDIT_ADAPTER_SUMMARY.txt +278 -0
- ciris_adapters/reddit/REDDIT_ANALYSIS_INDEX.md +307 -0
- ciris_adapters/reddit/REDDIT_PRODUCTION_READINESS_PLAN.md +518 -0
- ciris_adapters/reddit/__init__.py +15 -0
- ciris_adapters/reddit/adapter.py +189 -0
- ciris_adapters/reddit/configurable.py +274 -0
- ciris_adapters/reddit/error_handler.py +307 -0
- ciris_adapters/reddit/manifest.json +218 -0
- ciris_adapters/reddit/observer.py +532 -0
- ciris_adapters/reddit/protocol.py +34 -0
- ciris_adapters/reddit/schemas.py +433 -0
- ciris_adapters/reddit/service.py +1471 -0
- ciris_adapters/sample_adapter/README.md +474 -0
- ciris_adapters/sample_adapter/__init__.py +45 -0
- ciris_adapters/sample_adapter/adapter.py +208 -0
- ciris_adapters/sample_adapter/configurable.py +469 -0
- ciris_adapters/sample_adapter/manifest.json +247 -0
- ciris_adapters/sample_adapter/services.py +486 -0
- ciris_adapters/weather/__init__.py +16 -0
- ciris_adapters/weather/adapter.py +130 -0
- ciris_adapters/weather/configurable.py +240 -0
- ciris_adapters/weather/manifest.json +156 -0
- ciris_adapters/weather/service.py +600 -0
- ciris_agent-1.7.7.dist-info/METADATA +284 -0
- ciris_agent-1.7.7.dist-info/RECORD +986 -0
- ciris_agent-1.7.7.dist-info/WHEEL +5 -0
- ciris_agent-1.7.7.dist-info/entry_points.txt +15 -0
- ciris_agent-1.7.7.dist-info/licenses/LICENSE +205 -0
- ciris_agent-1.7.7.dist-info/licenses/NOTICE +82 -0
- ciris_agent-1.7.7.dist-info/top_level.txt +4 -0
- ciris_engine/__init__.py +15 -0
- ciris_engine/ciris_templates/ally.yaml +632 -0
- ciris_engine/ciris_templates/default.yaml +411 -0
- ciris_engine/ciris_templates/echo-core.yaml +629 -0
- ciris_engine/ciris_templates/echo-speculative.yaml +764 -0
- ciris_engine/ciris_templates/echo.yaml +647 -0
- ciris_engine/ciris_templates/sage.yaml +332 -0
- ciris_engine/ciris_templates/scout.yaml +338 -0
- ciris_engine/ciris_templates/test.yaml +168 -0
- ciris_engine/cli.py +42 -0
- ciris_engine/config/CIRIS_SERVICES.json +19 -0
- ciris_engine/config/MODEL_CAPABILITIES.json +419 -0
- ciris_engine/config/PRICING_DATA.json +179 -0
- ciris_engine/config/__init__.py +50 -0
- ciris_engine/config/ciris_services.py +113 -0
- ciris_engine/config/model_capabilities.py +388 -0
- ciris_engine/config/pricing_models.py +276 -0
- ciris_engine/constants.py +35 -0
- ciris_engine/data/__init__.py +1 -0
- ciris_engine/data/covenant_1.0b.txt +978 -0
- ciris_engine/gui_static/11steps.svg +107 -0
- ciris_engine/gui_static/2x-schematics.png +0 -0
- ciris_engine/gui_static/404/index.html +1 -0
- ciris_engine/gui_static/404.html +1 -0
- ciris_engine/gui_static/_next/static/0edhkwDxd5UccTsCmtaBi/_buildManifest.js +1 -0
- ciris_engine/gui_static/_next/static/0edhkwDxd5UccTsCmtaBi/_ssgManifest.js +1 -0
- ciris_engine/gui_static/_next/static/U-3xTQao7hc2wnAi-Uekm/_buildManifest.js +1 -0
- ciris_engine/gui_static/_next/static/U-3xTQao7hc2wnAi-Uekm/_ssgManifest.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/3297-60e86ba0f8a7b040.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/3835-2aad4b7f5f8e4643.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/4499-99a0bc47de0b8975.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/4534-af88cd4ba6e99bff.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/4541-84b455f9e0dc4cfe.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/4789-61412711484754bb.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/6539-c6398bc9d7018430.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/704-8e827b26cc8c2d32.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/704-fb45d630f3192c6f.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/8072-de4952a2e6d2b33f.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/8315-b91d03a3949db0af.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/8386-f93a83ccbd789bd9.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/87c73c54-781a7f35148d5433.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/8903-fefea3339a02d41b.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/9090-e66485adf8d9d990.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/_not-found/page-a67d9808462c23b1.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/account/api-keys/page-2d7ee1583bbbd02e.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/account/api-keys/page-6a3c2bae6fe92b7b.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/account/consent/page-2ed3a035136bc4e8.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/account/consent/page-b2f5c91844a32422.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/account/page-25b90f89af3ea58c.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/account/page-b65d16c94ecaf69c.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/account/privacy/page-675b6d05c8f9184f.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/account/privacy/page-cbee2e1c8ab52145.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/account/settings/page-0f44da06697cf9f0.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/account/settings/page-563420253577edbf.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/adapters/page-1854631018bc32be.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/agents/page-8353752c176a7c70.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/agents/page-f61a529f110a6040.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/api-demo/page-7f19b9d20d39be28.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/api-demo/page-d1063938f249b8bd.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/audit/page-321b6728b8fff0bb.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/audit/page-ebac35ca961a1277.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/billing/page-6f3dc3bd02924f8e.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/billing/page-fa4a469f814c821a.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/comms/page-0d4f734269addd8f.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/comms/page-79227d426050089c.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/config/page-018d21d683b6e5bc.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/config/page-2aa5a5363ca2a371.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/consent/page-198373205fd316e2.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/consent/page-f2ca39e7713b13f8.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/dashboard/page-1dd5a196f643c60d.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/dashboard/page-530a04d3abbb8cda.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/docs/page-3193b06d094ab654.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/docs/page-330e996dedb87aba.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/layout-0a70f5fc460298b1.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/layout-21f2f99dd5b336e9.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/login/page-33240e6c6034a49d.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/login/page-68ffab6d54a7fdcd.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/logs/page-8a6167aecc4a475c.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/memory/page-9ca8c5d0056de3ff.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/memory/page-e961226941c18f81.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/page-6fdb065a787a4974.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/page-89f87d431be6064a.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/runtime/page-2e728b9c43aa164d.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/runtime/page-c7dd033dc40a72f0.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/services/page-ae9f0bdf11d01a95.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/services/page-b10feb79ca5d75e5.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/sessions/page-13ebe7ef1c16ae11.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/sessions/page-e6c82b16d617f785.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/setup/page-0beb5f5b5a5c20fc.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/setup/page-2595e729eae30c0e.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/status-dashboard/page-1037c987aecc3653.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/status-dashboard/page-2ffd147f6d3162ff.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/system/page-2c5798d58cafcd91.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/system/page-505b1ba4eceb01c3.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/test-auth/page-b0cad31d5cb1b2fa.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/test-auth/page-f3ecd7a8012df230.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/test-login/page-f35117fdc4105801.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/test-login/page-fb583a7924114906.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/test-sdk/page-50f116fd76935563.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/test-sdk/page-c37d8aa5ba623a44.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/tools/page-429aec7a707777ef.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/tools/page-5f705aad60e0c04e.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/users/page-13476b8b0f3808cc.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/users/page-7e500d154ed5bba4.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/wa/page-cc4a9d8a5cb44d08.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/wa/page-ec3e429efbc79230.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/framework-9d29490f5ba089ba.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/main-1f554952e47a82c4.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/main-app-26fa8aed029082e5.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/main-app-97b0486ef6bcef25.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/pages/_app-6ce685456e616eb2.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/pages/_error-d4bce98d93fe21e7.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/webpack-fcebd240b7f8477d.js +1 -0
- ciris_engine/gui_static/_next/static/css/16b94b1fe0cc6e37.css +3 -0
- ciris_engine/gui_static/_next/static/css/77a24ceaae86deff.css +3 -0
- ciris_engine/gui_static/_next/static/media/4cf2300e9c8272f7-s.p.woff2 +0 -0
- ciris_engine/gui_static/_next/static/media/747892c23ea88013-s.woff2 +0 -0
- ciris_engine/gui_static/_next/static/media/8d697b304b401681-s.woff2 +0 -0
- ciris_engine/gui_static/_next/static/media/93f479601ee12b01-s.p.woff2 +0 -0
- ciris_engine/gui_static/_next/static/media/9610d9e46709d722-s.woff2 +0 -0
- ciris_engine/gui_static/_next/static/media/ba015fad6dcf6784-s.woff2 +0 -0
- ciris_engine/gui_static/_next/static/media/d8298875641ec7d4-s.p.woff2 +0 -0
- ciris_engine/gui_static/account/api-keys/index.html +1 -0
- ciris_engine/gui_static/account/api-keys/index.txt +27 -0
- ciris_engine/gui_static/account/consent/index.html +1 -0
- ciris_engine/gui_static/account/consent/index.txt +27 -0
- ciris_engine/gui_static/account/index.html +1 -0
- ciris_engine/gui_static/account/index.txt +27 -0
- ciris_engine/gui_static/account/privacy/index.html +1 -0
- ciris_engine/gui_static/account/privacy/index.txt +27 -0
- ciris_engine/gui_static/account/settings/index.html +1 -0
- ciris_engine/gui_static/account/settings/index.txt +27 -0
- ciris_engine/gui_static/adapters/index.html +1 -0
- ciris_engine/gui_static/adapters/index.txt +27 -0
- ciris_engine/gui_static/agents/index.html +1 -0
- ciris_engine/gui_static/agents/index.txt +27 -0
- ciris_engine/gui_static/andrew-roberts-euBRXcx57T4-unsplash.jpg +0 -0
- ciris_engine/gui_static/api-demo/index.html +1 -0
- ciris_engine/gui_static/api-demo/index.txt +27 -0
- ciris_engine/gui_static/audit/index.html +1 -0
- ciris_engine/gui_static/audit/index.txt +27 -0
- ciris_engine/gui_static/billing/index.html +1 -0
- ciris_engine/gui_static/billing/index.txt +27 -0
- ciris_engine/gui_static/blurryinfo.png +0 -0
- ciris_engine/gui_static/chip-vincent-PkQDwfl9Flc-unsplash.jpg +0 -0
- ciris_engine/gui_static/ciris-architecture.svg +338 -0
- ciris_engine/gui_static/comms/index.html +1 -0
- ciris_engine/gui_static/comms/index.txt +27 -0
- ciris_engine/gui_static/config/index.html +1 -0
- ciris_engine/gui_static/config/index.txt +27 -0
- ciris_engine/gui_static/consent/index.html +1 -0
- ciris_engine/gui_static/consent/index.txt +27 -0
- ciris_engine/gui_static/dashboard/index.html +1 -0
- ciris_engine/gui_static/dashboard/index.txt +27 -0
- ciris_engine/gui_static/docs/index.html +1 -0
- ciris_engine/gui_static/docs/index.txt +27 -0
- ciris_engine/gui_static/eric.png +0 -0
- ciris_engine/gui_static/file.svg +1 -0
- ciris_engine/gui_static/globe.svg +1 -0
- ciris_engine/gui_static/index.html +1 -0
- ciris_engine/gui_static/index.txt +27 -0
- ciris_engine/gui_static/infogfx-1@2x.png +0 -0
- ciris_engine/gui_static/infogfx-2.png +0 -0
- ciris_engine/gui_static/infogfx-dark-1.png +0 -0
- ciris_engine/gui_static/kelly-vohs-soSTXmIxTDU-unsplash.jpg +0 -0
- ciris_engine/gui_static/login/index.html +1 -0
- ciris_engine/gui_static/login/index.txt +27 -0
- ciris_engine/gui_static/logs/index.html +1 -0
- ciris_engine/gui_static/logs/index.txt +27 -0
- ciris_engine/gui_static/memory/index.html +1 -0
- ciris_engine/gui_static/memory/index.txt +27 -0
- ciris_engine/gui_static/nathan-farrish-ArcTfEoBgzs-unsplash.jpg +0 -0
- ciris_engine/gui_static/next.svg +1 -0
- ciris_engine/gui_static/overview.svg +512 -0
- ciris_engine/gui_static/overview1.svg +407 -0
- ciris_engine/gui_static/overview2.svg +370 -0
- ciris_engine/gui_static/pipeline-visualization.svg +278 -0
- ciris_engine/gui_static/privacy-policy.html +160 -0
- ciris_engine/gui_static/runtime/index.html +8 -0
- ciris_engine/gui_static/runtime/index.txt +27 -0
- ciris_engine/gui_static/services/index.html +1 -0
- ciris_engine/gui_static/services/index.txt +27 -0
- ciris_engine/gui_static/sessions/index.html +1 -0
- ciris_engine/gui_static/sessions/index.txt +27 -0
- ciris_engine/gui_static/setup/index.html +1 -0
- ciris_engine/gui_static/setup/index.txt +27 -0
- ciris_engine/gui_static/status-dashboard/index.html +1 -0
- ciris_engine/gui_static/status-dashboard/index.txt +27 -0
- ciris_engine/gui_static/system/index.html +1 -0
- ciris_engine/gui_static/system/index.txt +27 -0
- ciris_engine/gui_static/terms-of-service.html +174 -0
- ciris_engine/gui_static/test-auth/index.html +1 -0
- ciris_engine/gui_static/test-auth/index.txt +27 -0
- ciris_engine/gui_static/test-login/index.html +1 -0
- ciris_engine/gui_static/test-login/index.txt +27 -0
- ciris_engine/gui_static/test-sdk/index.html +1 -0
- ciris_engine/gui_static/test-sdk/index.txt +27 -0
- ciris_engine/gui_static/tools/index.html +1 -0
- ciris_engine/gui_static/tools/index.txt +27 -0
- ciris_engine/gui_static/users/index.html +1 -0
- ciris_engine/gui_static/users/index.txt +27 -0
- ciris_engine/gui_static/vercel.svg +1 -0
- ciris_engine/gui_static/videos/video1.mp4 +0 -0
- ciris_engine/gui_static/videos/video3.mp4 +0 -0
- ciris_engine/gui_static/wa/index.html +1 -0
- ciris_engine/gui_static/wa/index.txt +27 -0
- ciris_engine/gui_static/window.svg +1 -0
- ciris_engine/logic/__init__.py +8 -0
- ciris_engine/logic/adapters/__init__.py +74 -0
- ciris_engine/logic/adapters/api/__init__.py +5 -0
- ciris_engine/logic/adapters/api/adapter.py +1037 -0
- ciris_engine/logic/adapters/api/api_communication.py +370 -0
- ciris_engine/logic/adapters/api/api_document.py +330 -0
- ciris_engine/logic/adapters/api/api_observer.py +24 -0
- ciris_engine/logic/adapters/api/api_runtime_control.py +388 -0
- ciris_engine/logic/adapters/api/api_tools.py +299 -0
- ciris_engine/logic/adapters/api/api_vision.py +215 -0
- ciris_engine/logic/adapters/api/app.py +272 -0
- ciris_engine/logic/adapters/api/auth.py +159 -0
- ciris_engine/logic/adapters/api/config.py +101 -0
- ciris_engine/logic/adapters/api/constants.py +55 -0
- ciris_engine/logic/adapters/api/dependencies/__init__.py +1 -0
- ciris_engine/logic/adapters/api/dependencies/auth.py +260 -0
- ciris_engine/logic/adapters/api/endpoints/__init__.py +1 -0
- ciris_engine/logic/adapters/api/endpoints/emergency.py +86 -0
- ciris_engine/logic/adapters/api/middleware/__init__.py +1 -0
- ciris_engine/logic/adapters/api/middleware/rate_limiter.py +302 -0
- ciris_engine/logic/adapters/api/models.py +29 -0
- ciris_engine/logic/adapters/api/routes/__init__.py +52 -0
- ciris_engine/logic/adapters/api/routes/agent.py +1762 -0
- ciris_engine/logic/adapters/api/routes/audit.py +707 -0
- ciris_engine/logic/adapters/api/routes/auth.py +1745 -0
- ciris_engine/logic/adapters/api/routes/billing.py +895 -0
- ciris_engine/logic/adapters/api/routes/config.py +329 -0
- ciris_engine/logic/adapters/api/routes/connectors.py +534 -0
- ciris_engine/logic/adapters/api/routes/consent.py +637 -0
- ciris_engine/logic/adapters/api/routes/dsar.py +637 -0
- ciris_engine/logic/adapters/api/routes/dsar_multi_source.py +484 -0
- ciris_engine/logic/adapters/api/routes/emergency.py +302 -0
- ciris_engine/logic/adapters/api/routes/memory.py +733 -0
- ciris_engine/logic/adapters/api/routes/memory_filters.py +230 -0
- ciris_engine/logic/adapters/api/routes/memory_models.py +112 -0
- ciris_engine/logic/adapters/api/routes/memory_queries.py +236 -0
- ciris_engine/logic/adapters/api/routes/memory_query_helpers.py +394 -0
- ciris_engine/logic/adapters/api/routes/memory_visualization.py +359 -0
- ciris_engine/logic/adapters/api/routes/memory_visualization_helpers.py +110 -0
- ciris_engine/logic/adapters/api/routes/partnership.py +541 -0
- ciris_engine/logic/adapters/api/routes/setup.py +1374 -0
- ciris_engine/logic/adapters/api/routes/system.py +3049 -0
- ciris_engine/logic/adapters/api/routes/system_extensions.py +952 -0
- ciris_engine/logic/adapters/api/routes/telemetry.py +1987 -0
- ciris_engine/logic/adapters/api/routes/telemetry_converters.py +141 -0
- ciris_engine/logic/adapters/api/routes/telemetry_helpers.py +111 -0
- ciris_engine/logic/adapters/api/routes/telemetry_logs_reader.py +280 -0
- ciris_engine/logic/adapters/api/routes/telemetry_metrics.py +131 -0
- ciris_engine/logic/adapters/api/routes/telemetry_models.py +190 -0
- ciris_engine/logic/adapters/api/routes/telemetry_otlp.py +878 -0
- ciris_engine/logic/adapters/api/routes/telemetry_resource_helpers.py +191 -0
- ciris_engine/logic/adapters/api/routes/tickets.py +541 -0
- ciris_engine/logic/adapters/api/routes/tools.py +556 -0
- ciris_engine/logic/adapters/api/routes/transparency.py +281 -0
- ciris_engine/logic/adapters/api/routes/users.py +981 -0
- ciris_engine/logic/adapters/api/routes/verification.py +373 -0
- ciris_engine/logic/adapters/api/routes/wa.py +369 -0
- ciris_engine/logic/adapters/api/service_configuration.py +177 -0
- ciris_engine/logic/adapters/api/services/__init__.py +1 -0
- ciris_engine/logic/adapters/api/services/auth_service.py +1417 -0
- ciris_engine/logic/adapters/api/services/oauth_security.py +68 -0
- ciris_engine/logic/adapters/base.py +141 -0
- ciris_engine/logic/adapters/base_adapter.py +73 -0
- ciris_engine/logic/adapters/base_observer.py +1141 -0
- ciris_engine/logic/adapters/base_vision.py +312 -0
- ciris_engine/logic/adapters/cirisnode_client.py +307 -0
- ciris_engine/logic/adapters/cli/__init__.py +3 -0
- ciris_engine/logic/adapters/cli/adapter.py +207 -0
- ciris_engine/logic/adapters/cli/cli_adapter.py +902 -0
- ciris_engine/logic/adapters/cli/cli_observer.py +268 -0
- ciris_engine/logic/adapters/cli/cli_tools.py +427 -0
- ciris_engine/logic/adapters/cli/cli_wa_service.py +134 -0
- ciris_engine/logic/adapters/cli/config.py +73 -0
- ciris_engine/logic/adapters/discord/__init__.py +3 -0
- ciris_engine/logic/adapters/discord/adapter.py +783 -0
- ciris_engine/logic/adapters/discord/ciris_discord_client.py +159 -0
- ciris_engine/logic/adapters/discord/config.py +177 -0
- ciris_engine/logic/adapters/discord/constants.py +185 -0
- ciris_engine/logic/adapters/discord/discord-stubs.pyi +50 -0
- ciris_engine/logic/adapters/discord/discord_adapter.py +1584 -0
- ciris_engine/logic/adapters/discord/discord_audit.py +150 -0
- ciris_engine/logic/adapters/discord/discord_channel_manager.py +351 -0
- ciris_engine/logic/adapters/discord/discord_connection_manager.py +313 -0
- ciris_engine/logic/adapters/discord/discord_embed_formatter.py +369 -0
- ciris_engine/logic/adapters/discord/discord_error_classifier.py +302 -0
- ciris_engine/logic/adapters/discord/discord_error_handler.py +316 -0
- ciris_engine/logic/adapters/discord/discord_guidance_handler.py +460 -0
- ciris_engine/logic/adapters/discord/discord_message_handler.py +207 -0
- ciris_engine/logic/adapters/discord/discord_observer.py +670 -0
- ciris_engine/logic/adapters/discord/discord_rate_limiter.py +249 -0
- ciris_engine/logic/adapters/discord/discord_reaction_handler.py +278 -0
- ciris_engine/logic/adapters/discord/discord_tool_handler.py +465 -0
- ciris_engine/logic/adapters/discord/discord_tool_service.py +790 -0
- ciris_engine/logic/adapters/discord/discord_tools.py +90 -0
- ciris_engine/logic/adapters/discord/discord_vision_helper.py +148 -0
- ciris_engine/logic/adapters/discord/py.typed +0 -0
- ciris_engine/logic/adapters/document_parser.py +320 -0
- ciris_engine/logic/audit/__init__.py +10 -0
- ciris_engine/logic/audit/hash_chain.py +313 -0
- ciris_engine/logic/audit/signature_manager.py +352 -0
- ciris_engine/logic/audit/verifier.py +408 -0
- ciris_engine/logic/buses/__init__.py +21 -0
- ciris_engine/logic/buses/base_bus.py +178 -0
- ciris_engine/logic/buses/bus_manager.py +121 -0
- ciris_engine/logic/buses/communication_bus.py +387 -0
- ciris_engine/logic/buses/llm_bus.py +722 -0
- ciris_engine/logic/buses/memory_bus.py +577 -0
- ciris_engine/logic/buses/prohibitions.py +502 -0
- ciris_engine/logic/buses/runtime_control_bus.py +539 -0
- ciris_engine/logic/buses/tool_bus.py +482 -0
- ciris_engine/logic/buses/wise_bus.py +684 -0
- ciris_engine/logic/config/__init__.py +25 -0
- ciris_engine/logic/config/bootstrap.py +255 -0
- ciris_engine/logic/config/config_accessor.py +202 -0
- ciris_engine/logic/config/db_paths.py +194 -0
- ciris_engine/logic/config/env_utils.py +39 -0
- ciris_engine/logic/conscience/__init__.py +16 -0
- ciris_engine/logic/conscience/build_deferral_package.py +0 -0
- ciris_engine/logic/conscience/core.py +688 -0
- ciris_engine/logic/conscience/interface.py +33 -0
- ciris_engine/logic/conscience/registry.py +76 -0
- ciris_engine/logic/conscience/thought_depth_guardrail.py +231 -0
- ciris_engine/logic/conscience/updated_status_conscience.py +156 -0
- ciris_engine/logic/context/__init__.py +10 -0
- ciris_engine/logic/context/batch_context.py +550 -0
- ciris_engine/logic/context/builder.py +149 -0
- ciris_engine/logic/context/channel_resolution.py +136 -0
- ciris_engine/logic/context/secrets_snapshot.py +52 -0
- ciris_engine/logic/context/system_snapshot.py +116 -0
- ciris_engine/logic/context/system_snapshot_helpers.py +1651 -0
- ciris_engine/logic/covenant/__init__.py +33 -0
- ciris_engine/logic/covenant/executor.py +303 -0
- ciris_engine/logic/covenant/extractor.py +382 -0
- ciris_engine/logic/covenant/handler.py +241 -0
- ciris_engine/logic/covenant/verifier.py +383 -0
- ciris_engine/logic/dma/__init__.py +15 -0
- ciris_engine/logic/dma/action_selection/__init__.py +11 -0
- ciris_engine/logic/dma/action_selection/action_instruction_generator.py +444 -0
- ciris_engine/logic/dma/action_selection/context_builder.py +508 -0
- ciris_engine/logic/dma/action_selection/faculty_integration.py +193 -0
- ciris_engine/logic/dma/action_selection/special_cases.py +132 -0
- ciris_engine/logic/dma/action_selection_pdma.py +365 -0
- ciris_engine/logic/dma/base_dma.py +335 -0
- ciris_engine/logic/dma/csdma.py +239 -0
- ciris_engine/logic/dma/dma_executor.py +575 -0
- ciris_engine/logic/dma/dsdma_base.py +410 -0
- ciris_engine/logic/dma/exceptions.py +4 -0
- ciris_engine/logic/dma/factory.py +150 -0
- ciris_engine/logic/dma/pdma.py +120 -0
- ciris_engine/logic/dma/prompt_loader.py +189 -0
- ciris_engine/logic/dma/prompts/action_selection_pdma.yml +58 -0
- ciris_engine/logic/dma/prompts/csdma_common_sense.yml +28 -0
- ciris_engine/logic/dma/prompts/dsdma_base.yml +17 -0
- ciris_engine/logic/dma/prompts/pdma_ethical.yml +42 -0
- ciris_engine/logic/formatters/__init__.py +26 -0
- ciris_engine/logic/formatters/crisis_resources.py +80 -0
- ciris_engine/logic/formatters/escalation.py +21 -0
- ciris_engine/logic/formatters/identity.py +224 -0
- ciris_engine/logic/formatters/prompt_blocks.py +64 -0
- ciris_engine/logic/formatters/system_snapshot.py +193 -0
- ciris_engine/logic/formatters/user_profiles.py +108 -0
- ciris_engine/logic/handlers/__init__.py +1 -0
- ciris_engine/logic/handlers/control/__init__.py +1 -0
- ciris_engine/logic/handlers/control/defer_handler.py +195 -0
- ciris_engine/logic/handlers/control/ponder_handler.py +154 -0
- ciris_engine/logic/handlers/control/reject_handler.py +81 -0
- ciris_engine/logic/handlers/external/__init__.py +1 -0
- ciris_engine/logic/handlers/external/observe_handler.py +154 -0
- ciris_engine/logic/handlers/external/speak_handler.py +250 -0
- ciris_engine/logic/handlers/external/tool_handler.py +148 -0
- ciris_engine/logic/handlers/memory/__init__.py +1 -0
- ciris_engine/logic/handlers/memory/forget_handler.py +107 -0
- ciris_engine/logic/handlers/memory/memorize_handler.py +391 -0
- ciris_engine/logic/handlers/memory/recall_handler.py +213 -0
- ciris_engine/logic/handlers/terminal/__init__.py +1 -0
- ciris_engine/logic/handlers/terminal/task_complete_handler.py +299 -0
- ciris_engine/logic/infrastructure/__init__.py +1 -0
- ciris_engine/logic/infrastructure/handlers/__init__.py +8 -0
- ciris_engine/logic/infrastructure/handlers/action_dispatcher.py +382 -0
- ciris_engine/logic/infrastructure/handlers/base_handler.py +450 -0
- ciris_engine/logic/infrastructure/handlers/exceptions.py +2 -0
- ciris_engine/logic/infrastructure/handlers/handler_registry.py +59 -0
- ciris_engine/logic/infrastructure/handlers/helpers.py +55 -0
- ciris_engine/logic/infrastructure/step_streaming.py +149 -0
- ciris_engine/logic/infrastructure/sub_services/__init__.py +1 -0
- ciris_engine/logic/infrastructure/sub_services/identity_variance_monitor.py +1035 -0
- ciris_engine/logic/infrastructure/sub_services/pattern_analysis_loop.py +758 -0
- ciris_engine/logic/infrastructure/sub_services/wa_cli_bootstrap.py +229 -0
- ciris_engine/logic/infrastructure/sub_services/wa_cli_display.py +176 -0
- ciris_engine/logic/infrastructure/sub_services/wa_cli_oauth.py +404 -0
- ciris_engine/logic/infrastructure/sub_services/wa_cli_wizard.py +181 -0
- ciris_engine/logic/persistence/__init__.py +130 -0
- ciris_engine/logic/persistence/analytics.py +97 -0
- ciris_engine/logic/persistence/db/__init__.py +28 -0
- ciris_engine/logic/persistence/db/core.py +520 -0
- ciris_engine/logic/persistence/db/dialect.py +380 -0
- ciris_engine/logic/persistence/db/execution_helpers.py +216 -0
- ciris_engine/logic/persistence/db/migration_runner.py +191 -0
- ciris_engine/logic/persistence/db/operations.py +313 -0
- ciris_engine/logic/persistence/db/query_builder.py +232 -0
- ciris_engine/logic/persistence/db/retry.py +154 -0
- ciris_engine/logic/persistence/db/setup.py +18 -0
- ciris_engine/logic/persistence/migrations/postgres/001_initial_schema.sql +4 -0
- ciris_engine/logic/persistence/migrations/postgres/002_add_retry_status.sql +3 -0
- ciris_engine/logic/persistence/migrations/postgres/003_add_task_update_tracking.sql +8 -0
- ciris_engine/logic/persistence/migrations/postgres/004_add_occurrence_id.sql +54 -0
- ciris_engine/logic/persistence/migrations/postgres/005_add_consolidation_locks.sql +22 -0
- ciris_engine/logic/persistence/migrations/postgres/006_add_correlation_id_unique_index.sql +16 -0
- ciris_engine/logic/persistence/migrations/postgres/007_add_dsar_tickets.sql +39 -0
- ciris_engine/logic/persistence/migrations/postgres/008_rename_to_tickets_add_sop.sql +123 -0
- ciris_engine/logic/persistence/migrations/postgres/009_add_ticket_status_columns.sql +39 -0
- ciris_engine/logic/persistence/migrations/postgres/010_add_images_to_tasks.sql +5 -0
- ciris_engine/logic/persistence/migrations/sqlite/001_initial_schema.sql +357 -0
- ciris_engine/logic/persistence/migrations/sqlite/002_add_retry_status.sql +3 -0
- ciris_engine/logic/persistence/migrations/sqlite/003_add_task_update_tracking.sql +8 -0
- ciris_engine/logic/persistence/migrations/sqlite/004_add_occurrence_id.sql +45 -0
- ciris_engine/logic/persistence/migrations/sqlite/005_add_consolidation_locks.sql +22 -0
- ciris_engine/logic/persistence/migrations/sqlite/006_add_correlation_id_unique_index.sql +16 -0
- ciris_engine/logic/persistence/migrations/sqlite/007_add_dsar_tickets.sql +39 -0
- ciris_engine/logic/persistence/migrations/sqlite/008_rename_to_tickets_add_sop.sql +120 -0
- ciris_engine/logic/persistence/migrations/sqlite/009_add_ticket_status_columns.sql +129 -0
- ciris_engine/logic/persistence/migrations/sqlite/010_add_images_to_tasks.sql +17 -0
- ciris_engine/logic/persistence/models/__init__.py +141 -0
- ciris_engine/logic/persistence/models/correlations.py +881 -0
- ciris_engine/logic/persistence/models/deferral.py +68 -0
- ciris_engine/logic/persistence/models/dsar.py +286 -0
- ciris_engine/logic/persistence/models/graph.py +362 -0
- ciris_engine/logic/persistence/models/identity.py +264 -0
- ciris_engine/logic/persistence/models/queue_status.py +139 -0
- ciris_engine/logic/persistence/models/tasks.py +1043 -0
- ciris_engine/logic/persistence/models/thoughts.py +400 -0
- ciris_engine/logic/persistence/models/tickets.py +518 -0
- ciris_engine/logic/persistence/stores/__init__.py +13 -0
- ciris_engine/logic/persistence/stores/auth_helpers.py +117 -0
- ciris_engine/logic/persistence/stores/authentication_store.py +414 -0
- ciris_engine/logic/persistence/utils.py +212 -0
- ciris_engine/logic/processors/__init__.py +30 -0
- ciris_engine/logic/processors/core/__init__.py +1 -0
- ciris_engine/logic/processors/core/base_processor.py +280 -0
- ciris_engine/logic/processors/core/main_processor.py +1777 -0
- ciris_engine/logic/processors/core/step_decorators.py +1583 -0
- ciris_engine/logic/processors/core/thought_processor/__init__.py +20 -0
- ciris_engine/logic/processors/core/thought_processor/action_execution.py +49 -0
- ciris_engine/logic/processors/core/thought_processor/conscience_execution.py +382 -0
- ciris_engine/logic/processors/core/thought_processor/finalize_action.py +66 -0
- ciris_engine/logic/processors/core/thought_processor/gather_context.py +120 -0
- ciris_engine/logic/processors/core/thought_processor/main.py +920 -0
- ciris_engine/logic/processors/core/thought_processor/perform_aspdma.py +86 -0
- ciris_engine/logic/processors/core/thought_processor/perform_dmas.py +106 -0
- ciris_engine/logic/processors/core/thought_processor/recursive_processing.py +237 -0
- ciris_engine/logic/processors/core/thought_processor/round_complete.py +52 -0
- ciris_engine/logic/processors/core/thought_processor/start_round.py +64 -0
- ciris_engine/logic/processors/exceptions.py +59 -0
- ciris_engine/logic/processors/states/__init__.py +1 -0
- ciris_engine/logic/processors/states/dream_processor.py +1381 -0
- ciris_engine/logic/processors/states/play_processor.py +141 -0
- ciris_engine/logic/processors/states/shutdown_processor.py +623 -0
- ciris_engine/logic/processors/states/solitude_processor.py +305 -0
- ciris_engine/logic/processors/states/wakeup_processor.py +802 -0
- ciris_engine/logic/processors/states/work_processor.py +742 -0
- ciris_engine/logic/processors/support/__init__.py +1 -0
- ciris_engine/logic/processors/support/dma_orchestrator.py +336 -0
- ciris_engine/logic/processors/support/processing_queue.py +133 -0
- ciris_engine/logic/processors/support/shutdown_condition_evaluator.py +294 -0
- ciris_engine/logic/processors/support/state_manager.py +358 -0
- ciris_engine/logic/processors/support/task_manager.py +303 -0
- ciris_engine/logic/processors/support/thought_escalation.py +116 -0
- ciris_engine/logic/processors/support/thought_manager.py +328 -0
- ciris_engine/logic/processors/support/thought_manager_enhanced.py +105 -0
- ciris_engine/logic/registries/__init__.py +34 -0
- ciris_engine/logic/registries/base.py +653 -0
- ciris_engine/logic/registries/circuit_breaker.py +275 -0
- ciris_engine/logic/registries/typed_registries.py +184 -0
- ciris_engine/logic/runtime/__init__.py +7 -0
- ciris_engine/logic/runtime/adapter_loader.py +261 -0
- ciris_engine/logic/runtime/adapter_manager.py +1053 -0
- ciris_engine/logic/runtime/ciris_runtime.py +2342 -0
- ciris_engine/logic/runtime/ciris_runtime_helpers.py +923 -0
- ciris_engine/logic/runtime/component_builder.py +361 -0
- ciris_engine/logic/runtime/identity_manager.py +219 -0
- ciris_engine/logic/runtime/module_loader.py +207 -0
- ciris_engine/logic/runtime/prevent_sideeffects.py +30 -0
- ciris_engine/logic/runtime/runtime_interface.py +23 -0
- ciris_engine/logic/runtime/service_initializer.py +1623 -0
- ciris_engine/logic/secrets/__init__.py +30 -0
- ciris_engine/logic/secrets/encryption.py +175 -0
- ciris_engine/logic/secrets/filter.py +295 -0
- ciris_engine/logic/secrets/service.py +652 -0
- ciris_engine/logic/secrets/store.py +669 -0
- ciris_engine/logic/services/__init__.py +1 -0
- ciris_engine/logic/services/adaptation/__init__.py +3 -0
- ciris_engine/logic/services/base_graph_service.py +142 -0
- ciris_engine/logic/services/base_infrastructure_service.py +69 -0
- ciris_engine/logic/services/base_scheduled_service.py +136 -0
- ciris_engine/logic/services/base_service.py +247 -0
- ciris_engine/logic/services/governance/__init__.py +3 -0
- ciris_engine/logic/services/governance/adaptive_filter/__init__.py +14 -0
- ciris_engine/logic/services/governance/adaptive_filter/service.py +818 -0
- ciris_engine/logic/services/governance/consent/__init__.py +53 -0
- ciris_engine/logic/services/governance/consent/air.py +403 -0
- ciris_engine/logic/services/governance/consent/decay.py +324 -0
- ciris_engine/logic/services/governance/consent/dsar_automation.py +589 -0
- ciris_engine/logic/services/governance/consent/exceptions.py +106 -0
- ciris_engine/logic/services/governance/consent/metrics.py +270 -0
- ciris_engine/logic/services/governance/consent/partnership.py +533 -0
- ciris_engine/logic/services/governance/consent/service.py +1256 -0
- ciris_engine/logic/services/governance/dsar/__init__.py +29 -0
- ciris_engine/logic/services/governance/dsar/orchestrator.py +977 -0
- ciris_engine/logic/services/governance/dsar/schemas.py +141 -0
- ciris_engine/logic/services/governance/dsar/signature_service.py +283 -0
- ciris_engine/logic/services/governance/self_observation/__init__.py +20 -0
- ciris_engine/logic/services/governance/self_observation/service.py +1153 -0
- ciris_engine/logic/services/governance/visibility/__init__.py +17 -0
- ciris_engine/logic/services/governance/visibility/service.py +512 -0
- ciris_engine/logic/services/governance/wise_authority/__init__.py +15 -0
- ciris_engine/logic/services/governance/wise_authority/service.py +827 -0
- ciris_engine/logic/services/graph/__init__.py +5 -0
- ciris_engine/logic/services/graph/audit_service/__init__.py +5 -0
- ciris_engine/logic/services/graph/audit_service/service.py +1675 -0
- ciris_engine/logic/services/graph/base.py +208 -0
- ciris_engine/logic/services/graph/config_service/__init__.py +5 -0
- ciris_engine/logic/services/graph/config_service/service.py +372 -0
- ciris_engine/logic/services/graph/incident_service/__init__.py +5 -0
- ciris_engine/logic/services/graph/incident_service/service.py +803 -0
- ciris_engine/logic/services/graph/memory_service.py +1120 -0
- ciris_engine/logic/services/graph/telemetry_service/__init__.py +5 -0
- ciris_engine/logic/services/graph/telemetry_service/exceptions.py +104 -0
- ciris_engine/logic/services/graph/telemetry_service/helpers.py +1337 -0
- ciris_engine/logic/services/graph/telemetry_service/service.py +2429 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/__init__.py +17 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/aggregation_helpers.py +355 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/cleanup_helpers.py +438 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/compressor.py +260 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/consolidators/__init__.py +27 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/consolidators/audit.py +326 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/consolidators/conversation.py +291 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/consolidators/memory.py +197 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/consolidators/metrics.py +251 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/consolidators/task.py +257 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/consolidators/trace.py +363 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/data_converter.py +545 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/date_calculation_helpers.py +193 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/db_query_helpers.py +296 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/edge_helpers.py +92 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/edge_manager.py +896 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/extensive_helpers.py +322 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/period_manager.py +152 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/profound_helpers.py +277 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/query_manager.py +812 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/service.py +1692 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/sql_builders.py +363 -0
- ciris_engine/logic/services/infrastructure/__init__.py +1 -0
- ciris_engine/logic/services/infrastructure/authentication/__init__.py +5 -0
- ciris_engine/logic/services/infrastructure/authentication/service.py +1634 -0
- ciris_engine/logic/services/infrastructure/database_maintenance/__init__.py +15 -0
- ciris_engine/logic/services/infrastructure/database_maintenance/service.py +764 -0
- ciris_engine/logic/services/infrastructure/resource_monitor/__init__.py +7 -0
- ciris_engine/logic/services/infrastructure/resource_monitor/ciris_billing_provider.py +755 -0
- ciris_engine/logic/services/infrastructure/resource_monitor/service.py +409 -0
- ciris_engine/logic/services/infrastructure/resource_monitor/simple_credit_provider.py +129 -0
- ciris_engine/logic/services/lifecycle/__init__.py +3 -0
- ciris_engine/logic/services/lifecycle/initialization/__init__.py +10 -0
- ciris_engine/logic/services/lifecycle/initialization/service.py +312 -0
- ciris_engine/logic/services/lifecycle/scheduler/__init__.py +5 -0
- ciris_engine/logic/services/lifecycle/scheduler/service.py +607 -0
- ciris_engine/logic/services/lifecycle/shutdown/__init__.py +9 -0
- ciris_engine/logic/services/lifecycle/shutdown/service.py +378 -0
- ciris_engine/logic/services/lifecycle/time/__init__.py +15 -0
- ciris_engine/logic/services/lifecycle/time/service.py +259 -0
- ciris_engine/logic/services/memory_service/__init__.py +8 -0
- ciris_engine/logic/services/mixins/__init__.py +13 -0
- ciris_engine/logic/services/mixins/example_usage.py +200 -0
- ciris_engine/logic/services/mixins/request_metrics.py +179 -0
- ciris_engine/logic/services/runtime/__init__.py +3 -0
- ciris_engine/logic/services/runtime/adapter_configuration/__init__.py +16 -0
- ciris_engine/logic/services/runtime/adapter_configuration/service.py +674 -0
- ciris_engine/logic/services/runtime/adapter_configuration/session.py +67 -0
- ciris_engine/logic/services/runtime/control_service/__init__.py +5 -0
- ciris_engine/logic/services/runtime/control_service/service.py +2269 -0
- ciris_engine/logic/services/runtime/llm_service/__init__.py +14 -0
- ciris_engine/logic/services/runtime/llm_service/pricing_calculator.py +279 -0
- ciris_engine/logic/services/runtime/llm_service/service.py +930 -0
- ciris_engine/logic/services/tools/__init__.py +5 -0
- ciris_engine/logic/services/tools/core_tool_service/__init__.py +8 -0
- ciris_engine/logic/services/tools/core_tool_service/service.py +852 -0
- ciris_engine/logic/setup/__init__.py +1 -0
- ciris_engine/logic/setup/first_run.py +250 -0
- ciris_engine/logic/setup/wizard.py +327 -0
- ciris_engine/logic/telemetry/__init__.py +46 -0
- ciris_engine/logic/telemetry/core.py +239 -0
- ciris_engine/logic/telemetry/hot_cold_config.py +133 -0
- ciris_engine/logic/telemetry/log_collector.py +190 -0
- ciris_engine/logic/telemetry/resource_monitor.py +7 -0
- ciris_engine/logic/telemetry/security.py +79 -0
- ciris_engine/logic/utils/__init__.py +18 -0
- ciris_engine/logic/utils/channel_utils.py +75 -0
- ciris_engine/logic/utils/consent/__init__.py +1 -0
- ciris_engine/logic/utils/consent/partnership_utils.py +172 -0
- ciris_engine/logic/utils/constants.py +92 -0
- ciris_engine/logic/utils/context_utils.py +145 -0
- ciris_engine/logic/utils/directory_setup.py +533 -0
- ciris_engine/logic/utils/graphql_context_provider.py +152 -0
- ciris_engine/logic/utils/identity_resolution.py +843 -0
- ciris_engine/logic/utils/incident_capture_handler.py +303 -0
- ciris_engine/logic/utils/initialization_manager.py +74 -0
- ciris_engine/logic/utils/jsondict_helpers.py +290 -0
- ciris_engine/logic/utils/log_sanitizer.py +97 -0
- ciris_engine/logic/utils/logging_config.py +151 -0
- ciris_engine/logic/utils/observability_decorators.py +544 -0
- ciris_engine/logic/utils/occurrence_utils.py +155 -0
- ciris_engine/logic/utils/path_resolution.py +281 -0
- ciris_engine/logic/utils/platform_detection.py +286 -0
- ciris_engine/logic/utils/privacy.py +266 -0
- ciris_engine/logic/utils/profile_loader.py +124 -0
- ciris_engine/logic/utils/profile_manager.py +16 -0
- ciris_engine/logic/utils/runtime_utils.py +69 -0
- ciris_engine/logic/utils/shutdown_manager.py +107 -0
- ciris_engine/logic/utils/task_formatters.py +60 -0
- ciris_engine/logic/utils/task_thought_factory.py +404 -0
- ciris_engine/logic/utils/thought_utils.py +54 -0
- ciris_engine/logic/utils/user_utils.py +70 -0
- ciris_engine/protocols/__init__.py +0 -0
- ciris_engine/protocols/adapters/__init__.py +35 -0
- ciris_engine/protocols/adapters/base.py +149 -0
- ciris_engine/protocols/adapters/configurable.py +265 -0
- ciris_engine/protocols/adapters/message.py +90 -0
- ciris_engine/protocols/audit/__init__.py +1 -0
- ciris_engine/protocols/buses/__init__.py +1 -0
- ciris_engine/protocols/config/__init__.py +1 -0
- ciris_engine/protocols/conscience/__init__.py +1 -0
- ciris_engine/protocols/consent.py +88 -0
- ciris_engine/protocols/context/__init__.py +1 -0
- ciris_engine/protocols/data/__init__.py +1 -0
- ciris_engine/protocols/dma/__init__.py +1 -0
- ciris_engine/protocols/dma/base.py +107 -0
- ciris_engine/protocols/faculties.py +34 -0
- ciris_engine/protocols/formatters/__init__.py +1 -0
- ciris_engine/protocols/handlers/__init__.py +1 -0
- ciris_engine/protocols/infrastructure/__init__.py +25 -0
- ciris_engine/protocols/infrastructure/base.py +377 -0
- ciris_engine/protocols/persistence/__init__.py +1 -0
- ciris_engine/protocols/pipeline_control.py +609 -0
- ciris_engine/protocols/processors/__init__.py +19 -0
- ciris_engine/protocols/processors/agent.py +299 -0
- ciris_engine/protocols/processors/base.py +130 -0
- ciris_engine/protocols/processors/orchestration.py +62 -0
- ciris_engine/protocols/registries/__init__.py +1 -0
- ciris_engine/protocols/runtime/__init__.py +1 -0
- ciris_engine/protocols/runtime/base.py +163 -0
- ciris_engine/protocols/secrets/__init__.py +1 -0
- ciris_engine/protocols/services/__init__.py +80 -0
- ciris_engine/protocols/services/adaptation/__init__.py +7 -0
- ciris_engine/protocols/services/adaptation/self_observation.py +265 -0
- ciris_engine/protocols/services/governance/__init__.py +20 -0
- ciris_engine/protocols/services/governance/communication.py +58 -0
- ciris_engine/protocols/services/governance/filter.py +56 -0
- ciris_engine/protocols/services/governance/visibility.py +32 -0
- ciris_engine/protocols/services/governance/wa_auth.py +192 -0
- ciris_engine/protocols/services/governance/wise_authority.py +75 -0
- ciris_engine/protocols/services/graph/__init__.py +19 -0
- ciris_engine/protocols/services/graph/audit.py +92 -0
- ciris_engine/protocols/services/graph/config.py +54 -0
- ciris_engine/protocols/services/graph/incident_management.py +103 -0
- ciris_engine/protocols/services/graph/memory.py +110 -0
- ciris_engine/protocols/services/graph/telemetry.py +51 -0
- ciris_engine/protocols/services/graph/tsdb_consolidation.py +87 -0
- ciris_engine/protocols/services/infrastructure/__init__.py +11 -0
- ciris_engine/protocols/services/infrastructure/authentication.py +159 -0
- ciris_engine/protocols/services/infrastructure/credit_gate.py +46 -0
- ciris_engine/protocols/services/infrastructure/database_maintenance.py +25 -0
- ciris_engine/protocols/services/infrastructure/resource_monitor.py +83 -0
- ciris_engine/protocols/services/lifecycle/__init__.py +13 -0
- ciris_engine/protocols/services/lifecycle/initialization.py +41 -0
- ciris_engine/protocols/services/lifecycle/scheduler.py +42 -0
- ciris_engine/protocols/services/lifecycle/shutdown.py +50 -0
- ciris_engine/protocols/services/lifecycle/time.py +31 -0
- ciris_engine/protocols/services/runtime/__init__.py +13 -0
- ciris_engine/protocols/services/runtime/llm.py +50 -0
- ciris_engine/protocols/services/runtime/runtime_control.py +193 -0
- ciris_engine/protocols/services/runtime/secrets.py +100 -0
- ciris_engine/protocols/services/runtime/tool.py +123 -0
- ciris_engine/protocols/telemetry/__init__.py +1 -0
- ciris_engine/protocols/utils/__init__.py +1 -0
- ciris_engine/schemas/__init__.py +112 -0
- ciris_engine/schemas/actions/__init__.py +37 -0
- ciris_engine/schemas/actions/parameters.py +137 -0
- ciris_engine/schemas/adapters/__init__.py +13 -0
- ciris_engine/schemas/adapters/cirisnode.py +135 -0
- ciris_engine/schemas/adapters/cli.py +97 -0
- ciris_engine/schemas/adapters/cli_tools.py +98 -0
- ciris_engine/schemas/adapters/discord.py +125 -0
- ciris_engine/schemas/adapters/graphql_core.py +144 -0
- ciris_engine/schemas/adapters/registration.py +47 -0
- ciris_engine/schemas/adapters/runtime_context.py +48 -0
- ciris_engine/schemas/adapters/tool_execution.py +45 -0
- ciris_engine/schemas/adapters/tools.py +96 -0
- ciris_engine/schemas/api/__init__.py +1 -0
- ciris_engine/schemas/api/agent.py +50 -0
- ciris_engine/schemas/api/audit.py +38 -0
- ciris_engine/schemas/api/auth.py +351 -0
- ciris_engine/schemas/api/config_security.py +242 -0
- ciris_engine/schemas/api/emergency.py +111 -0
- ciris_engine/schemas/api/responses.py +72 -0
- ciris_engine/schemas/api/runtime.py +26 -0
- ciris_engine/schemas/api/telemetry.py +109 -0
- ciris_engine/schemas/api/wa.py +90 -0
- ciris_engine/schemas/audit/__init__.py +13 -0
- ciris_engine/schemas/audit/core.py +139 -0
- ciris_engine/schemas/audit/hash_chain.py +58 -0
- ciris_engine/schemas/audit/verification.py +131 -0
- ciris_engine/schemas/buses/__init__.py +1 -0
- ciris_engine/schemas/config/__init__.py +41 -0
- ciris_engine/schemas/config/agent.py +279 -0
- ciris_engine/schemas/config/cognitive_state_behaviors.py +194 -0
- ciris_engine/schemas/config/default_dsar_sops.py +178 -0
- ciris_engine/schemas/config/essential.py +195 -0
- ciris_engine/schemas/config/tickets.py +86 -0
- ciris_engine/schemas/conscience/__init__.py +25 -0
- ciris_engine/schemas/conscience/context.py +34 -0
- ciris_engine/schemas/conscience/core.py +145 -0
- ciris_engine/schemas/conscience/results.py +24 -0
- ciris_engine/schemas/consent/__init__.py +5 -0
- ciris_engine/schemas/consent/core.py +404 -0
- ciris_engine/schemas/context/__init__.py +1 -0
- ciris_engine/schemas/covenant.py +382 -0
- ciris_engine/schemas/data/__init__.py +1 -0
- ciris_engine/schemas/dma/__init__.py +16 -0
- ciris_engine/schemas/dma/core.py +199 -0
- ciris_engine/schemas/dma/faculty.py +192 -0
- ciris_engine/schemas/dma/prompts.py +172 -0
- ciris_engine/schemas/dma/results.py +103 -0
- ciris_engine/schemas/formatters/__init__.py +1 -0
- ciris_engine/schemas/handlers/__init__.py +10 -0
- ciris_engine/schemas/handlers/context.py +119 -0
- ciris_engine/schemas/handlers/contexts.py +100 -0
- ciris_engine/schemas/handlers/core.py +167 -0
- ciris_engine/schemas/handlers/memory_schemas.py +67 -0
- ciris_engine/schemas/handlers/schemas.py +95 -0
- ciris_engine/schemas/identity.py +149 -0
- ciris_engine/schemas/infrastructure/__init__.py +1 -0
- ciris_engine/schemas/infrastructure/base.py +256 -0
- ciris_engine/schemas/infrastructure/behavioral_patterns.py +129 -0
- ciris_engine/schemas/infrastructure/feedback_loop.py +57 -0
- ciris_engine/schemas/infrastructure/identity_variance.py +141 -0
- ciris_engine/schemas/infrastructure/oauth.py +175 -0
- ciris_engine/schemas/infrastructure/wa_cli_wizard.py +54 -0
- ciris_engine/schemas/persistence/__init__.py +34 -0
- ciris_engine/schemas/persistence/core.py +140 -0
- ciris_engine/schemas/persistence/correlations.py +73 -0
- ciris_engine/schemas/persistence/postgres/__init__.py +1 -0
- ciris_engine/schemas/persistence/postgres/tables.py +280 -0
- ciris_engine/schemas/persistence/sqlite/__init__.py +1 -0
- ciris_engine/schemas/persistence/sqlite/tables.py +281 -0
- ciris_engine/schemas/platform.py +149 -0
- ciris_engine/schemas/processors/__init__.py +26 -0
- ciris_engine/schemas/processors/base.py +130 -0
- ciris_engine/schemas/processors/cognitive.py +77 -0
- ciris_engine/schemas/processors/context.py +35 -0
- ciris_engine/schemas/processors/core.py +152 -0
- ciris_engine/schemas/processors/dma.py +105 -0
- ciris_engine/schemas/processors/error.py +122 -0
- ciris_engine/schemas/processors/main.py +109 -0
- ciris_engine/schemas/processors/phase_results.py +21 -0
- ciris_engine/schemas/processors/results.py +99 -0
- ciris_engine/schemas/processors/solitude.py +79 -0
- ciris_engine/schemas/processors/state.py +202 -0
- ciris_engine/schemas/processors/state_example.py +177 -0
- ciris_engine/schemas/processors/states.py +21 -0
- ciris_engine/schemas/processors/status.py +34 -0
- ciris_engine/schemas/registries/__init__.py +1 -0
- ciris_engine/schemas/registries/base.py +66 -0
- ciris_engine/schemas/resources/__init__.py +15 -0
- ciris_engine/schemas/resources/crisis.py +315 -0
- ciris_engine/schemas/runtime/__init__.py +42 -0
- ciris_engine/schemas/runtime/adapter_management.py +186 -0
- ciris_engine/schemas/runtime/api.py +58 -0
- ciris_engine/schemas/runtime/audit.py +50 -0
- ciris_engine/schemas/runtime/bootstrap.py +33 -0
- ciris_engine/schemas/runtime/contexts.py +61 -0
- ciris_engine/schemas/runtime/core.py +161 -0
- ciris_engine/schemas/runtime/enums.py +167 -0
- ciris_engine/schemas/runtime/extended.py +232 -0
- ciris_engine/schemas/runtime/manifest.py +311 -0
- ciris_engine/schemas/runtime/memory.py +60 -0
- ciris_engine/schemas/runtime/messages.py +108 -0
- ciris_engine/schemas/runtime/models.py +156 -0
- ciris_engine/schemas/runtime/processing_context.py +43 -0
- ciris_engine/schemas/runtime/protocols_core.py +96 -0
- ciris_engine/schemas/runtime/resources.py +33 -0
- ciris_engine/schemas/runtime/system_context.py +417 -0
- ciris_engine/schemas/secrets/__init__.py +1 -0
- ciris_engine/schemas/secrets/core.py +267 -0
- ciris_engine/schemas/secrets/service.py +95 -0
- ciris_engine/schemas/services/__init__.py +33 -0
- ciris_engine/schemas/services/audit_summary_node.py +172 -0
- ciris_engine/schemas/services/authority/__init__.py +39 -0
- ciris_engine/schemas/services/authority/jwt.py +158 -0
- ciris_engine/schemas/services/authority/wa_updates.py +138 -0
- ciris_engine/schemas/services/authority/wise_authority.py +163 -0
- ciris_engine/schemas/services/authority_core.py +370 -0
- ciris_engine/schemas/services/capabilities.py +72 -0
- ciris_engine/schemas/services/community_core.py +95 -0
- ciris_engine/schemas/services/context.py +111 -0
- ciris_engine/schemas/services/conversation_summary_node.py +189 -0
- ciris_engine/schemas/services/core/__init__.py +153 -0
- ciris_engine/schemas/services/core/runtime.py +262 -0
- ciris_engine/schemas/services/core/runtime_config.py +117 -0
- ciris_engine/schemas/services/core/secrets.py +65 -0
- ciris_engine/schemas/services/correlation_node.py +179 -0
- ciris_engine/schemas/services/credit_gate.py +92 -0
- ciris_engine/schemas/services/discord_nodes.py +299 -0
- ciris_engine/schemas/services/feedback_core.py +131 -0
- ciris_engine/schemas/services/filters_core.py +270 -0
- ciris_engine/schemas/services/governance.py +26 -0
- ciris_engine/schemas/services/graph/__init__.py +26 -0
- ciris_engine/schemas/services/graph/attributes.py +254 -0
- ciris_engine/schemas/services/graph/audit.py +98 -0
- ciris_engine/schemas/services/graph/consolidation.py +338 -0
- ciris_engine/schemas/services/graph/edge_types.py +43 -0
- ciris_engine/schemas/services/graph/edges.py +88 -0
- ciris_engine/schemas/services/graph/incident.py +312 -0
- ciris_engine/schemas/services/graph/memory.py +84 -0
- ciris_engine/schemas/services/graph/node_data.py +174 -0
- ciris_engine/schemas/services/graph/query_results.py +82 -0
- ciris_engine/schemas/services/graph/telemetry.py +250 -0
- ciris_engine/schemas/services/graph/tsdb_consolidation.py +27 -0
- ciris_engine/schemas/services/graph/tsdb_models.py +107 -0
- ciris_engine/schemas/services/graph_core.py +196 -0
- ciris_engine/schemas/services/graph_typed_nodes.py +194 -0
- ciris_engine/schemas/services/infrastructure/__init__.py +1 -0
- ciris_engine/schemas/services/infrastructure/resource_monitor.py +20 -0
- ciris_engine/schemas/services/lifecycle/__init__.py +9 -0
- ciris_engine/schemas/services/lifecycle/initialization.py +33 -0
- ciris_engine/schemas/services/lifecycle/time.py +50 -0
- ciris_engine/schemas/services/llm.py +187 -0
- ciris_engine/schemas/services/metadata.py +43 -0
- ciris_engine/schemas/services/nodes.py +704 -0
- ciris_engine/schemas/services/operations.py +126 -0
- ciris_engine/schemas/services/requests.py +128 -0
- ciris_engine/schemas/services/resources_core.py +182 -0
- ciris_engine/schemas/services/runtime_control.py +1010 -0
- ciris_engine/schemas/services/shutdown.py +88 -0
- ciris_engine/schemas/services/special/__init__.py +0 -0
- ciris_engine/schemas/services/special/self_observation.py +396 -0
- ciris_engine/schemas/services/trace_summary_node.py +199 -0
- ciris_engine/schemas/services/visibility.py +98 -0
- ciris_engine/schemas/streaming/__init__.py +10 -0
- ciris_engine/schemas/streaming/reasoning_stream.py +95 -0
- ciris_engine/schemas/telemetry/__init__.py +0 -0
- ciris_engine/schemas/telemetry/collector.py +67 -0
- ciris_engine/schemas/telemetry/core.py +252 -0
- ciris_engine/schemas/telemetry/unified.py +59 -0
- ciris_engine/schemas/tools.py +72 -0
- ciris_engine/schemas/types.py +47 -0
- ciris_engine/schemas/utils/__init__.py +1 -0
- ciris_engine/schemas/utils/config_validator.py +54 -0
- ciris_engine/utils/__init__.py +1 -0
- ciris_engine/utils/serialization.py +35 -0
- ciris_sdk/__init__.py +124 -0
- ciris_sdk/auth_store.py +261 -0
- ciris_sdk/client.py +261 -0
- ciris_sdk/exceptions.py +73 -0
- ciris_sdk/model_types.py +258 -0
- ciris_sdk/models.py +354 -0
- ciris_sdk/pagination.py +214 -0
- ciris_sdk/rate_limiter.py +188 -0
- ciris_sdk/setup.py +17 -0
- ciris_sdk/telemetry_models.py +257 -0
- ciris_sdk/telemetry_responses.py +199 -0
- ciris_sdk/transport.py +177 -0
- ciris_sdk/websocket.py +400 -0
- main.py +766 -0
|
@@ -0,0 +1,981 @@
|
|
|
1
|
+
"""User management API routes."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from typing import Any, Dict, Generic, List, Optional, TypeVar
|
|
6
|
+
|
|
7
|
+
import aiofiles
|
|
8
|
+
from fastapi import APIRouter, Depends, HTTPException, Query, Request
|
|
9
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
10
|
+
|
|
11
|
+
from ciris_engine.schemas.api.auth import AuthContext, PermissionRequestResponse, PermissionRequestUser, UserRole
|
|
12
|
+
from ciris_engine.schemas.runtime.api import APIRole
|
|
13
|
+
from ciris_engine.schemas.services.authority_core import OAuthIdentityLink, WARole
|
|
14
|
+
from ciris_engine.schemas.types import JSONDict
|
|
15
|
+
|
|
16
|
+
from ..dependencies.auth import check_permissions, get_auth_context, get_auth_service
|
|
17
|
+
from ..services.auth_service import (
|
|
18
|
+
PERMISSION_MANAGE_USER_PERMISSIONS,
|
|
19
|
+
PERMISSION_USERS_DELETE,
|
|
20
|
+
PERMISSION_USERS_READ,
|
|
21
|
+
PERMISSION_USERS_WRITE,
|
|
22
|
+
PERMISSION_WA_MINT,
|
|
23
|
+
APIAuthService,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
# Error message constants
|
|
27
|
+
ERROR_USER_NOT_FOUND = "User not found"
|
|
28
|
+
ERROR_USERNAME_EXISTS = "Username already exists"
|
|
29
|
+
ERROR_CREATE_USER_FAILED = "Failed to create user"
|
|
30
|
+
ERROR_CHANGE_PASSWORD_FAILED = "Failed to change password. Check current password."
|
|
31
|
+
ERROR_CANNOT_DEMOTE_SELF = "Cannot demote your own role"
|
|
32
|
+
ERROR_CANNOT_DEACTIVATE_SELF = "Cannot deactivate your own account"
|
|
33
|
+
ERROR_ONLY_ADMIN_MINT_WA = "Only ADMIN or higher can mint Wise Authorities"
|
|
34
|
+
ERROR_CANNOT_MINT_ROOT = "Cannot mint new ROOT authorities. ROOT is singular."
|
|
35
|
+
ERROR_INVALID_SIGNATURE = "Invalid ROOT signature"
|
|
36
|
+
ERROR_SIGNATURE_OR_KEY_REQUIRED = "Either signature or private_key_path must be provided"
|
|
37
|
+
|
|
38
|
+
router = APIRouter(prefix="/users", tags=["users"])
|
|
39
|
+
|
|
40
|
+
logger = logging.getLogger(__name__)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# Generic models
|
|
44
|
+
T = TypeVar("T")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class PaginatedResponse(BaseModel, Generic[T]):
|
|
48
|
+
"""Generic paginated response."""
|
|
49
|
+
|
|
50
|
+
items: List[T]
|
|
51
|
+
total: int
|
|
52
|
+
page: int
|
|
53
|
+
page_size: int
|
|
54
|
+
pages: int
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# Request/Response models
|
|
58
|
+
class UserSummary(BaseModel):
|
|
59
|
+
"""Summary information about a user."""
|
|
60
|
+
|
|
61
|
+
user_id: str
|
|
62
|
+
username: str
|
|
63
|
+
auth_type: str = Field(description="password, oauth, api_key")
|
|
64
|
+
api_role: APIRole
|
|
65
|
+
wa_role: Optional[WARole] = None
|
|
66
|
+
wa_id: Optional[str] = None
|
|
67
|
+
oauth_provider: Optional[str] = None
|
|
68
|
+
oauth_email: Optional[str] = None
|
|
69
|
+
oauth_name: Optional[str] = None # Full name from OAuth provider
|
|
70
|
+
oauth_picture: Optional[str] = None # Profile picture URL
|
|
71
|
+
permission_requested_at: Optional[datetime] = None # Permission request timestamp
|
|
72
|
+
created_at: datetime
|
|
73
|
+
last_login: Optional[datetime] = None
|
|
74
|
+
is_active: bool = True
|
|
75
|
+
linked_oauth_accounts: List["LinkedOAuthAccount"] = Field(default_factory=list)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class UserDetail(UserSummary):
|
|
79
|
+
"""Detailed user information."""
|
|
80
|
+
|
|
81
|
+
permissions: List[str]
|
|
82
|
+
custom_permissions: Optional[List[str]] = None
|
|
83
|
+
oauth_external_id: Optional[str] = None
|
|
84
|
+
wa_parent_id: Optional[str] = None
|
|
85
|
+
wa_auto_minted: bool = False
|
|
86
|
+
api_keys_count: int = 0
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class LinkedOAuthAccount(BaseModel):
|
|
90
|
+
"""Linked OAuth identity summary."""
|
|
91
|
+
|
|
92
|
+
provider: str
|
|
93
|
+
external_id: str
|
|
94
|
+
account_name: Optional[str] = None
|
|
95
|
+
linked_at: Optional[datetime] = None
|
|
96
|
+
is_primary: bool = False
|
|
97
|
+
metadata: Dict[str, str] = Field(default_factory=dict)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _build_linked_accounts(user: Any) -> List[LinkedOAuthAccount]:
|
|
101
|
+
accounts: List[LinkedOAuthAccount] = []
|
|
102
|
+
seen = set()
|
|
103
|
+
|
|
104
|
+
for link in getattr(user, "oauth_links", []) or []:
|
|
105
|
+
key = (link.provider, link.external_id)
|
|
106
|
+
seen.add(key)
|
|
107
|
+
accounts.append(
|
|
108
|
+
LinkedOAuthAccount(
|
|
109
|
+
provider=link.provider,
|
|
110
|
+
external_id=link.external_id,
|
|
111
|
+
account_name=getattr(link, "account_name", None),
|
|
112
|
+
linked_at=getattr(link, "linked_at", None),
|
|
113
|
+
is_primary=getattr(link, "is_primary", False),
|
|
114
|
+
metadata=getattr(link, "metadata", {}) or {},
|
|
115
|
+
)
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
if user.oauth_provider and user.oauth_external_id:
|
|
119
|
+
key = (user.oauth_provider, user.oauth_external_id)
|
|
120
|
+
if key not in seen:
|
|
121
|
+
accounts.append(
|
|
122
|
+
LinkedOAuthAccount(
|
|
123
|
+
provider=user.oauth_provider,
|
|
124
|
+
external_id=user.oauth_external_id,
|
|
125
|
+
account_name=getattr(user, "oauth_name", None),
|
|
126
|
+
linked_at=None,
|
|
127
|
+
is_primary=True,
|
|
128
|
+
metadata={},
|
|
129
|
+
)
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
primary_found = False
|
|
133
|
+
for account in accounts:
|
|
134
|
+
if account.is_primary and not primary_found:
|
|
135
|
+
primary_found = True
|
|
136
|
+
else:
|
|
137
|
+
account.is_primary = account.is_primary and primary_found
|
|
138
|
+
|
|
139
|
+
if accounts and not any(account.is_primary for account in accounts):
|
|
140
|
+
accounts[0].is_primary = True
|
|
141
|
+
|
|
142
|
+
return accounts
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _build_user_detail(user_id: str, user: Any, auth_service: APIAuthService) -> UserDetail:
|
|
146
|
+
"""Build UserDetail response from user object (DRY helper)."""
|
|
147
|
+
# Use effective permissions which includes WA role inheritance
|
|
148
|
+
# ROOT WA users get AUTHORITY permissions (including wa.resolve_deferral)
|
|
149
|
+
permissions = auth_service.get_effective_permissions(user)
|
|
150
|
+
api_keys = auth_service.list_user_api_keys(user_id)
|
|
151
|
+
|
|
152
|
+
return UserDetail(
|
|
153
|
+
user_id=user_id,
|
|
154
|
+
username=user.name,
|
|
155
|
+
auth_type=user.auth_type,
|
|
156
|
+
api_role=user.api_role,
|
|
157
|
+
wa_role=user.wa_role,
|
|
158
|
+
wa_id=user.wa_id if user.wa_role else None,
|
|
159
|
+
oauth_provider=user.oauth_provider,
|
|
160
|
+
oauth_email=user.oauth_email,
|
|
161
|
+
oauth_name=user.oauth_name,
|
|
162
|
+
oauth_picture=user.oauth_picture,
|
|
163
|
+
permission_requested_at=user.permission_requested_at,
|
|
164
|
+
oauth_external_id=user.oauth_external_id,
|
|
165
|
+
created_at=user.created_at,
|
|
166
|
+
last_login=user.last_login,
|
|
167
|
+
is_active=user.is_active,
|
|
168
|
+
permissions=permissions,
|
|
169
|
+
custom_permissions=user.custom_permissions,
|
|
170
|
+
wa_parent_id=user.wa_parent_id,
|
|
171
|
+
wa_auto_minted=user.wa_auto_minted,
|
|
172
|
+
api_keys_count=len(api_keys),
|
|
173
|
+
linked_oauth_accounts=_build_linked_accounts(user),
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class UpdateUserRequest(BaseModel):
|
|
178
|
+
"""Request to update user information."""
|
|
179
|
+
|
|
180
|
+
api_role: Optional[APIRole] = None
|
|
181
|
+
is_active: Optional[bool] = None
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class ChangePasswordRequest(BaseModel):
|
|
185
|
+
"""Request to change user password."""
|
|
186
|
+
|
|
187
|
+
current_password: str
|
|
188
|
+
new_password: str
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class LinkOAuthAccountRequest(BaseModel):
|
|
192
|
+
"""Request payload to link an OAuth account to a user."""
|
|
193
|
+
|
|
194
|
+
provider: str = Field(..., description="OAuth provider identifier")
|
|
195
|
+
external_id: str = Field(..., description="Provider-scoped user identifier")
|
|
196
|
+
account_name: Optional[str] = Field(None, description="Display name provided by the provider")
|
|
197
|
+
metadata: Dict[str, str] = Field(default_factory=dict, description="Additional metadata from the provider")
|
|
198
|
+
primary: bool = Field(False, description="Promote this identity to the primary mapping")
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class CreateUserRequest(BaseModel):
|
|
202
|
+
"""Request to create a new user."""
|
|
203
|
+
|
|
204
|
+
username: str = Field(..., min_length=3, max_length=50)
|
|
205
|
+
password: str = Field(..., min_length=8)
|
|
206
|
+
api_role: APIRole = APIRole.OBSERVER
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class MintWARequest(BaseModel):
|
|
210
|
+
"""Request to mint user as Wise Authority."""
|
|
211
|
+
|
|
212
|
+
model_config = ConfigDict(
|
|
213
|
+
json_schema_extra={"example": {"wa_role": "AUTHORITY", "signature": "base64_encoded_signature"}}
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
wa_role: WARole = Field(description="Role to grant: AUTHORITY or OBSERVER")
|
|
217
|
+
signature: Optional[str] = Field(None, description="Ed25519 signature from ROOT private key")
|
|
218
|
+
private_key_path: Optional[str] = Field(None, description="Path to ROOT private key for auto-signing")
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class UpdatePermissionsRequest(BaseModel):
|
|
222
|
+
"""Request to update user's custom permissions."""
|
|
223
|
+
|
|
224
|
+
model_config = ConfigDict(json_schema_extra={"example": {"permissions": ["send_messages", "custom_permission_1"]}})
|
|
225
|
+
|
|
226
|
+
permissions: List[str] = Field(description="List of permission strings to grant")
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
class WAKeyCheckResponse(BaseModel):
|
|
230
|
+
"""Response for WA key existence check."""
|
|
231
|
+
|
|
232
|
+
exists: bool = Field(..., description="Whether the key file exists")
|
|
233
|
+
filename: str = Field(..., description="The filename that was checked")
|
|
234
|
+
error: Optional[str] = Field(None, description="Error message if any")
|
|
235
|
+
valid_size: Optional[bool] = Field(None, description="Whether the key has valid size (if exists)")
|
|
236
|
+
size: Optional[int] = Field(None, description="File size in bytes (if exists)")
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
class DeactivateUserResponse(BaseModel):
|
|
240
|
+
"""Response for user deactivation."""
|
|
241
|
+
|
|
242
|
+
message: str = Field(..., description="Success message")
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
class APIKeyInfo(BaseModel):
|
|
246
|
+
"""Information about an API key (masked for security)."""
|
|
247
|
+
|
|
248
|
+
key_id: str = Field(..., description="Unique key identifier")
|
|
249
|
+
masked_key: str = Field(..., description="Masked version of the key (e.g., 'ciris_****')")
|
|
250
|
+
created_at: datetime = Field(..., description="When the key was created")
|
|
251
|
+
last_used: Optional[datetime] = Field(None, description="When the key was last used")
|
|
252
|
+
is_active: bool = Field(..., description="Whether the key is currently active")
|
|
253
|
+
name: Optional[str] = Field(None, description="Optional name for the key")
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
class UserSettingsResponse(BaseModel):
|
|
257
|
+
"""User's personal settings (user-modifiable preferences)."""
|
|
258
|
+
|
|
259
|
+
user_preferred_name: Optional[str] = Field(None, description="User's preferred display name")
|
|
260
|
+
location: Optional[str] = Field(None, description="User's location preference")
|
|
261
|
+
interaction_preferences: Optional[str] = Field(None, description="User's custom interaction preferences")
|
|
262
|
+
marketing_opt_in: bool = Field(False, description="User consent for marketing communications")
|
|
263
|
+
marketing_opt_in_source: Optional[str] = Field(None, description="Source of marketing consent (read-only)")
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
class UpdateUserSettingsRequest(BaseModel):
|
|
267
|
+
"""Request to update user's personal settings."""
|
|
268
|
+
|
|
269
|
+
user_preferred_name: Optional[str] = Field(None, description="User's preferred display name")
|
|
270
|
+
location: Optional[str] = Field(None, description="User's location preference")
|
|
271
|
+
interaction_preferences: Optional[str] = Field(None, description="User's custom interaction preferences")
|
|
272
|
+
marketing_opt_in: Optional[bool] = Field(None, description="User consent for marketing communications")
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
@router.get("", response_model=PaginatedResponse[UserSummary])
|
|
276
|
+
async def list_users(
|
|
277
|
+
page: int = Query(1, ge=1),
|
|
278
|
+
page_size: int = Query(20, ge=1, le=100),
|
|
279
|
+
search: Optional[str] = None,
|
|
280
|
+
auth_type: Optional[str] = None,
|
|
281
|
+
api_role: Optional[APIRole] = None,
|
|
282
|
+
wa_role: Optional[WARole] = None,
|
|
283
|
+
is_active: Optional[bool] = None,
|
|
284
|
+
auth: AuthContext = Depends(get_auth_context),
|
|
285
|
+
auth_service: APIAuthService = Depends(get_auth_service),
|
|
286
|
+
_: None = Depends(check_permissions([PERMISSION_USERS_READ])),
|
|
287
|
+
) -> PaginatedResponse[UserSummary]:
|
|
288
|
+
"""
|
|
289
|
+
List all users with optional filtering.
|
|
290
|
+
|
|
291
|
+
Requires: users.read permission (ADMIN or higher)
|
|
292
|
+
"""
|
|
293
|
+
# Get all WA certificates from the auth service
|
|
294
|
+
users = await auth_service.list_users(
|
|
295
|
+
search=search, auth_type=auth_type, api_role=api_role, wa_role=wa_role, is_active=is_active
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
# Paginate results
|
|
299
|
+
total = len(users)
|
|
300
|
+
start = (page - 1) * page_size
|
|
301
|
+
end = start + page_size
|
|
302
|
+
|
|
303
|
+
# Convert to UserSummary objects
|
|
304
|
+
items = []
|
|
305
|
+
for user_id, user in users[start:end]:
|
|
306
|
+
items.append(
|
|
307
|
+
UserSummary(
|
|
308
|
+
user_id=user_id, # Use the actual user_id key, not wa_id
|
|
309
|
+
username=user.name,
|
|
310
|
+
auth_type=user.auth_type,
|
|
311
|
+
api_role=user.api_role,
|
|
312
|
+
wa_role=user.wa_role,
|
|
313
|
+
wa_id=user.wa_id if user.wa_role else None,
|
|
314
|
+
oauth_provider=user.oauth_provider,
|
|
315
|
+
oauth_email=user.oauth_email,
|
|
316
|
+
oauth_name=user.oauth_name,
|
|
317
|
+
oauth_picture=user.oauth_picture,
|
|
318
|
+
permission_requested_at=user.permission_requested_at,
|
|
319
|
+
created_at=user.created_at,
|
|
320
|
+
last_login=user.last_login,
|
|
321
|
+
is_active=user.is_active,
|
|
322
|
+
linked_oauth_accounts=_build_linked_accounts(user),
|
|
323
|
+
)
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
return PaginatedResponse(
|
|
327
|
+
items=items, total=total, page=page, page_size=page_size, pages=(total + page_size - 1) // page_size
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
@router.post("", response_model=UserDetail)
|
|
332
|
+
async def create_user(
|
|
333
|
+
request: CreateUserRequest,
|
|
334
|
+
auth: AuthContext = Depends(get_auth_context),
|
|
335
|
+
auth_service: APIAuthService = Depends(get_auth_service),
|
|
336
|
+
_: None = Depends(check_permissions([PERMISSION_USERS_WRITE])),
|
|
337
|
+
) -> UserDetail:
|
|
338
|
+
"""
|
|
339
|
+
Create a new user account.
|
|
340
|
+
|
|
341
|
+
Requires: users.write permission (SYSTEM_ADMIN only)
|
|
342
|
+
"""
|
|
343
|
+
# Check if username already exists
|
|
344
|
+
existing = auth_service.get_user_by_username(request.username)
|
|
345
|
+
if existing:
|
|
346
|
+
raise HTTPException(status_code=400, detail=ERROR_USERNAME_EXISTS)
|
|
347
|
+
|
|
348
|
+
# Create the user
|
|
349
|
+
user = await auth_service.create_user(
|
|
350
|
+
username=request.username, password=request.password, api_role=request.api_role
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
if not user:
|
|
354
|
+
raise HTTPException(status_code=500, detail=ERROR_CREATE_USER_FAILED)
|
|
355
|
+
|
|
356
|
+
# Return the created user details
|
|
357
|
+
return await get_user(user.wa_id, auth, auth_service, None)
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
@router.post("/request-permissions", response_model=PermissionRequestResponse)
|
|
361
|
+
async def request_permissions(
|
|
362
|
+
auth: AuthContext = Depends(get_auth_context), auth_service: APIAuthService = Depends(get_auth_service)
|
|
363
|
+
) -> PermissionRequestResponse:
|
|
364
|
+
"""
|
|
365
|
+
Request communication permissions for the current user.
|
|
366
|
+
|
|
367
|
+
Requires: Must be authenticated (any role)
|
|
368
|
+
"""
|
|
369
|
+
from ciris_engine.schemas.api.auth import Permission, PermissionRequestResponse
|
|
370
|
+
|
|
371
|
+
# Check if user already has SEND_MESSAGES permission
|
|
372
|
+
if auth.has_permission(Permission.SEND_MESSAGES):
|
|
373
|
+
return PermissionRequestResponse(
|
|
374
|
+
success=True, status="already_granted", message="You already have communication permissions."
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
# Get the current user
|
|
378
|
+
user = auth_service.get_user(auth.user_id)
|
|
379
|
+
if not user:
|
|
380
|
+
raise HTTPException(status_code=404, detail=ERROR_USER_NOT_FOUND)
|
|
381
|
+
|
|
382
|
+
# Check if request already pending
|
|
383
|
+
if user.permission_requested_at:
|
|
384
|
+
return PermissionRequestResponse(
|
|
385
|
+
success=True,
|
|
386
|
+
status="already_requested",
|
|
387
|
+
message="Your permission request is pending review.",
|
|
388
|
+
requested_at=user.permission_requested_at,
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
# Set permission request timestamp
|
|
392
|
+
user.permission_requested_at = datetime.now()
|
|
393
|
+
# Store the updated user
|
|
394
|
+
auth_service._users[user.wa_id] = user
|
|
395
|
+
|
|
396
|
+
logger.info(f"Permission request submitted by user ID: {user.wa_id}")
|
|
397
|
+
|
|
398
|
+
return PermissionRequestResponse(
|
|
399
|
+
success=True,
|
|
400
|
+
status="request_submitted",
|
|
401
|
+
message="Your request has been submitted for review.",
|
|
402
|
+
requested_at=user.permission_requested_at,
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
@router.get("/permission-requests", response_model=List[PermissionRequestUser])
|
|
407
|
+
async def get_permission_requests(
|
|
408
|
+
auth: AuthContext = Depends(get_auth_context),
|
|
409
|
+
auth_service: APIAuthService = Depends(get_auth_service),
|
|
410
|
+
include_granted: bool = Query(False, description="Include users who already have permissions"),
|
|
411
|
+
) -> List[PermissionRequestUser]:
|
|
412
|
+
"""
|
|
413
|
+
Get list of users who have requested permissions.
|
|
414
|
+
|
|
415
|
+
Requires: ADMIN role or higher
|
|
416
|
+
"""
|
|
417
|
+
from ciris_engine.schemas.api.auth import Permission, PermissionRequestUser
|
|
418
|
+
|
|
419
|
+
logger.info(f"Permission requests called by {auth.user_id} with role {auth.role}")
|
|
420
|
+
|
|
421
|
+
# Check permissions - require ADMIN or higher
|
|
422
|
+
if auth.role not in [UserRole.ADMIN, UserRole.AUTHORITY, UserRole.SYSTEM_ADMIN]:
|
|
423
|
+
logger.error(f"Insufficient permissions for user {auth.user_id} with role {auth.role}")
|
|
424
|
+
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
|
425
|
+
|
|
426
|
+
permission_requests = []
|
|
427
|
+
|
|
428
|
+
# Check all users for permission requests
|
|
429
|
+
logger.info(f"Checking {len(auth_service._users)} users in _users dict")
|
|
430
|
+
for user_id, user in auth_service._users.items():
|
|
431
|
+
# Skip users without permission requests
|
|
432
|
+
if not user.permission_requested_at:
|
|
433
|
+
continue
|
|
434
|
+
|
|
435
|
+
# Check if user has SEND_MESSAGES permission
|
|
436
|
+
has_send_messages = Permission.SEND_MESSAGES.value in (user.custom_permissions or [])
|
|
437
|
+
|
|
438
|
+
# Skip users who already have permissions unless include_granted is True
|
|
439
|
+
if has_send_messages and not include_granted:
|
|
440
|
+
continue
|
|
441
|
+
|
|
442
|
+
# Add to results
|
|
443
|
+
permission_requests.append(
|
|
444
|
+
PermissionRequestUser(
|
|
445
|
+
id=user.wa_id,
|
|
446
|
+
email=user.oauth_email,
|
|
447
|
+
oauth_name=user.oauth_name,
|
|
448
|
+
oauth_picture=user.oauth_picture,
|
|
449
|
+
role=UserRole.OBSERVER, # OAuth users are always OBSERVER initially
|
|
450
|
+
permission_requested_at=user.permission_requested_at,
|
|
451
|
+
has_send_messages=has_send_messages,
|
|
452
|
+
)
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
# Sort by request date (newest first)
|
|
456
|
+
permission_requests.sort(key=lambda x: x.permission_requested_at, reverse=True)
|
|
457
|
+
|
|
458
|
+
return permission_requests
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
@router.get("/me/settings", response_model=UserSettingsResponse)
|
|
462
|
+
async def get_my_settings(
|
|
463
|
+
request: Request,
|
|
464
|
+
auth: AuthContext = Depends(get_auth_context),
|
|
465
|
+
) -> UserSettingsResponse:
|
|
466
|
+
"""
|
|
467
|
+
Get the current user's personal settings.
|
|
468
|
+
|
|
469
|
+
Requires: Must be authenticated (any role)
|
|
470
|
+
"""
|
|
471
|
+
from ciris_engine.schemas.services.graph_core import GraphScope, NodeType
|
|
472
|
+
from ciris_engine.schemas.services.operations import MemoryQuery
|
|
473
|
+
|
|
474
|
+
# Get the memory service from app state
|
|
475
|
+
memory_service = getattr(request.app.state, "memory_service", None)
|
|
476
|
+
if not memory_service:
|
|
477
|
+
raise HTTPException(status_code=503, detail="Memory service not available")
|
|
478
|
+
|
|
479
|
+
try:
|
|
480
|
+
# Query the user node from the graph
|
|
481
|
+
user_query = MemoryQuery(
|
|
482
|
+
node_id=f"user/{auth.user_id}",
|
|
483
|
+
scope=GraphScope.LOCAL,
|
|
484
|
+
type=NodeType.USER,
|
|
485
|
+
include_edges=False,
|
|
486
|
+
depth=1,
|
|
487
|
+
)
|
|
488
|
+
user_results = await memory_service.recall(user_query)
|
|
489
|
+
|
|
490
|
+
if not user_results:
|
|
491
|
+
# User node doesn't exist yet - return defaults
|
|
492
|
+
return UserSettingsResponse()
|
|
493
|
+
|
|
494
|
+
user_node = user_results[0]
|
|
495
|
+
attrs = user_node.attributes if isinstance(user_node.attributes, dict) else {}
|
|
496
|
+
|
|
497
|
+
# Extract user settings from node attributes
|
|
498
|
+
return UserSettingsResponse(
|
|
499
|
+
user_preferred_name=attrs.get("user_preferred_name"),
|
|
500
|
+
location=attrs.get("location"),
|
|
501
|
+
interaction_preferences=attrs.get("interaction_preferences"),
|
|
502
|
+
marketing_opt_in=attrs.get("marketing_opt_in", False),
|
|
503
|
+
marketing_opt_in_source=attrs.get("marketing_opt_in_source"),
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
except Exception as e:
|
|
507
|
+
logger.error(f"Failed to retrieve user settings for {auth.user_id}: {e}")
|
|
508
|
+
raise HTTPException(status_code=500, detail="Failed to retrieve user settings")
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
@router.put("/me/settings", response_model=UserSettingsResponse)
|
|
512
|
+
async def update_my_settings(
|
|
513
|
+
http_request: Request,
|
|
514
|
+
request: UpdateUserSettingsRequest,
|
|
515
|
+
auth: AuthContext = Depends(get_auth_context),
|
|
516
|
+
) -> UserSettingsResponse:
|
|
517
|
+
"""
|
|
518
|
+
Update the current user's personal settings.
|
|
519
|
+
|
|
520
|
+
Requires: Must be authenticated (any role)
|
|
521
|
+
|
|
522
|
+
Note: This endpoint bypasses the MANAGED_USER_ATTRIBUTES protection
|
|
523
|
+
because users are allowed to modify their own settings.
|
|
524
|
+
"""
|
|
525
|
+
from ciris_engine.schemas.services.graph_core import GraphNode, GraphScope, NodeType
|
|
526
|
+
from ciris_engine.schemas.services.operations import MemoryQuery
|
|
527
|
+
|
|
528
|
+
# Get the memory service from app state
|
|
529
|
+
memory_service = getattr(http_request.app.state, "memory_service", None)
|
|
530
|
+
if not memory_service:
|
|
531
|
+
raise HTTPException(status_code=503, detail="Memory service not available")
|
|
532
|
+
|
|
533
|
+
try:
|
|
534
|
+
# Query the user node from the graph
|
|
535
|
+
user_query = MemoryQuery(
|
|
536
|
+
node_id=f"user/{auth.user_id}",
|
|
537
|
+
scope=GraphScope.LOCAL,
|
|
538
|
+
type=NodeType.USER,
|
|
539
|
+
include_edges=False,
|
|
540
|
+
depth=1,
|
|
541
|
+
)
|
|
542
|
+
user_results = await memory_service.recall(user_query)
|
|
543
|
+
|
|
544
|
+
# Prepare attributes to update (only include fields that were provided)
|
|
545
|
+
attrs_to_update: JSONDict = {}
|
|
546
|
+
if request.user_preferred_name is not None:
|
|
547
|
+
attrs_to_update["user_preferred_name"] = request.user_preferred_name
|
|
548
|
+
if request.location is not None:
|
|
549
|
+
attrs_to_update["location"] = request.location
|
|
550
|
+
if request.interaction_preferences is not None:
|
|
551
|
+
attrs_to_update["interaction_preferences"] = request.interaction_preferences
|
|
552
|
+
if request.marketing_opt_in is not None:
|
|
553
|
+
attrs_to_update["marketing_opt_in"] = request.marketing_opt_in
|
|
554
|
+
# Update marketing_opt_in_source when user changes their preference
|
|
555
|
+
attrs_to_update["marketing_opt_in_source"] = "settings_api"
|
|
556
|
+
|
|
557
|
+
if user_results:
|
|
558
|
+
# User node exists - update it
|
|
559
|
+
user_node = user_results[0]
|
|
560
|
+
existing_attrs = user_node.attributes if isinstance(user_node.attributes, dict) else {}
|
|
561
|
+
|
|
562
|
+
# Merge existing attributes with updates
|
|
563
|
+
updated_attrs = {**existing_attrs, **attrs_to_update}
|
|
564
|
+
|
|
565
|
+
# Create updated node
|
|
566
|
+
updated_node = GraphNode(
|
|
567
|
+
id=f"user/{auth.user_id}",
|
|
568
|
+
type=NodeType.USER,
|
|
569
|
+
scope=GraphScope.LOCAL,
|
|
570
|
+
attributes=updated_attrs,
|
|
571
|
+
)
|
|
572
|
+
|
|
573
|
+
# Save the updated node directly (bypassing MANAGED_USER_ATTRIBUTES check)
|
|
574
|
+
await memory_service.memorize(updated_node, handler_name="UserSettingsAPI")
|
|
575
|
+
|
|
576
|
+
else:
|
|
577
|
+
# User node doesn't exist - create it with the settings
|
|
578
|
+
new_node = GraphNode(
|
|
579
|
+
id=f"user/{auth.user_id}",
|
|
580
|
+
type=NodeType.USER,
|
|
581
|
+
scope=GraphScope.LOCAL,
|
|
582
|
+
attributes=attrs_to_update,
|
|
583
|
+
)
|
|
584
|
+
|
|
585
|
+
# Save the new node
|
|
586
|
+
await memory_service.memorize(new_node, handler_name="UserSettingsAPI")
|
|
587
|
+
|
|
588
|
+
# Return the updated settings
|
|
589
|
+
final_attrs = {**attrs_to_update}
|
|
590
|
+
if user_results:
|
|
591
|
+
user_node = user_results[0]
|
|
592
|
+
existing_attrs = user_node.attributes if isinstance(user_node.attributes, dict) else {}
|
|
593
|
+
final_attrs = {**existing_attrs, **attrs_to_update}
|
|
594
|
+
|
|
595
|
+
return UserSettingsResponse(
|
|
596
|
+
user_preferred_name=final_attrs.get("user_preferred_name"),
|
|
597
|
+
location=final_attrs.get("location"),
|
|
598
|
+
interaction_preferences=final_attrs.get("interaction_preferences"),
|
|
599
|
+
marketing_opt_in=final_attrs.get("marketing_opt_in", False),
|
|
600
|
+
marketing_opt_in_source=final_attrs.get("marketing_opt_in_source"),
|
|
601
|
+
)
|
|
602
|
+
|
|
603
|
+
except Exception as e:
|
|
604
|
+
logger.error(f"Failed to update user settings for {auth.user_id}: {e}")
|
|
605
|
+
raise HTTPException(status_code=500, detail="Failed to update user settings")
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
@router.get("/{user_id}", response_model=UserDetail)
|
|
609
|
+
async def get_user(
|
|
610
|
+
user_id: str,
|
|
611
|
+
auth: AuthContext = Depends(get_auth_context),
|
|
612
|
+
auth_service: APIAuthService = Depends(get_auth_service),
|
|
613
|
+
_: None = Depends(check_permissions([PERMISSION_USERS_READ])),
|
|
614
|
+
) -> UserDetail:
|
|
615
|
+
"""
|
|
616
|
+
Get detailed information about a specific user.
|
|
617
|
+
|
|
618
|
+
Requires: users.read permission (ADMIN or higher)
|
|
619
|
+
"""
|
|
620
|
+
user = auth_service.get_user(user_id)
|
|
621
|
+
if not user:
|
|
622
|
+
raise HTTPException(status_code=404, detail=ERROR_USER_NOT_FOUND)
|
|
623
|
+
|
|
624
|
+
return _build_user_detail(user_id, user, auth_service)
|
|
625
|
+
|
|
626
|
+
|
|
627
|
+
@router.put("/{user_id}", response_model=UserDetail)
|
|
628
|
+
async def update_user(
|
|
629
|
+
user_id: str,
|
|
630
|
+
request: UpdateUserRequest,
|
|
631
|
+
auth: AuthContext = Depends(get_auth_context),
|
|
632
|
+
auth_service: APIAuthService = Depends(get_auth_service),
|
|
633
|
+
_: None = Depends(check_permissions([PERMISSION_USERS_WRITE])),
|
|
634
|
+
) -> UserDetail:
|
|
635
|
+
"""
|
|
636
|
+
Update user information (role, active status).
|
|
637
|
+
|
|
638
|
+
Requires: users.write permission (SYSTEM_ADMIN only)
|
|
639
|
+
"""
|
|
640
|
+
# Prevent self-demotion
|
|
641
|
+
if user_id == auth.user_id and request.api_role:
|
|
642
|
+
if request.api_role.value < auth.role.value:
|
|
643
|
+
raise HTTPException(status_code=400, detail=ERROR_CANNOT_DEMOTE_SELF)
|
|
644
|
+
|
|
645
|
+
# Update user
|
|
646
|
+
user = await auth_service.update_user(user_id=user_id, api_role=request.api_role, is_active=request.is_active)
|
|
647
|
+
|
|
648
|
+
if not user:
|
|
649
|
+
raise HTTPException(status_code=404, detail=ERROR_USER_NOT_FOUND)
|
|
650
|
+
|
|
651
|
+
# Return updated user details
|
|
652
|
+
return await get_user(user_id, auth, auth_service, None)
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
@router.put("/{user_id}/password")
|
|
656
|
+
async def change_password(
|
|
657
|
+
user_id: str,
|
|
658
|
+
request: ChangePasswordRequest,
|
|
659
|
+
auth: AuthContext = Depends(get_auth_context),
|
|
660
|
+
auth_service: APIAuthService = Depends(get_auth_service),
|
|
661
|
+
) -> Dict[str, str]:
|
|
662
|
+
"""
|
|
663
|
+
Change user password.
|
|
664
|
+
|
|
665
|
+
Users can change their own password.
|
|
666
|
+
SYSTEM_ADMIN can change any password without knowing current.
|
|
667
|
+
"""
|
|
668
|
+
# Check permissions
|
|
669
|
+
if user_id != auth.user_id:
|
|
670
|
+
# Only SYSTEM_ADMIN can change other users' passwords
|
|
671
|
+
await check_permissions([PERMISSION_USERS_WRITE])(auth, auth_service)
|
|
672
|
+
# SYSTEM_ADMIN doesn't need to provide current password
|
|
673
|
+
success = await auth_service.change_password(
|
|
674
|
+
user_id=user_id, new_password=request.new_password, skip_current_check=True
|
|
675
|
+
)
|
|
676
|
+
else:
|
|
677
|
+
# Users changing their own password must provide current
|
|
678
|
+
success = await auth_service.change_password(
|
|
679
|
+
user_id=user_id, new_password=request.new_password, current_password=request.current_password
|
|
680
|
+
)
|
|
681
|
+
|
|
682
|
+
if not success:
|
|
683
|
+
raise HTTPException(status_code=400, detail=ERROR_CHANGE_PASSWORD_FAILED)
|
|
684
|
+
|
|
685
|
+
return {"message": "Password changed successfully"}
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
@router.post("/{user_id}/oauth-links", response_model=UserDetail)
|
|
689
|
+
async def link_oauth_account(
|
|
690
|
+
user_id: str,
|
|
691
|
+
request: LinkOAuthAccountRequest,
|
|
692
|
+
auth: AuthContext = Depends(get_auth_context),
|
|
693
|
+
auth_service: APIAuthService = Depends(get_auth_service),
|
|
694
|
+
) -> UserDetail:
|
|
695
|
+
"""
|
|
696
|
+
Link an OAuth identity to an existing user.
|
|
697
|
+
|
|
698
|
+
Users can link accounts to themselves without special permissions.
|
|
699
|
+
Only SYSTEM_ADMIN can link accounts to other users.
|
|
700
|
+
"""
|
|
701
|
+
# Check permissions: users can link to their own account, SYSTEM_ADMIN can link to any
|
|
702
|
+
if user_id != auth.user_id:
|
|
703
|
+
await check_permissions([PERMISSION_USERS_WRITE])(auth, auth_service)
|
|
704
|
+
|
|
705
|
+
user = await auth_service.link_user_oauth(
|
|
706
|
+
wa_id=user_id,
|
|
707
|
+
provider=request.provider,
|
|
708
|
+
external_id=request.external_id,
|
|
709
|
+
account_name=request.account_name,
|
|
710
|
+
metadata=request.metadata,
|
|
711
|
+
primary=request.primary,
|
|
712
|
+
)
|
|
713
|
+
|
|
714
|
+
if not user:
|
|
715
|
+
raise HTTPException(status_code=404, detail=ERROR_USER_NOT_FOUND)
|
|
716
|
+
|
|
717
|
+
return _build_user_detail(user_id, user, auth_service)
|
|
718
|
+
|
|
719
|
+
|
|
720
|
+
@router.delete("/{user_id}/oauth-links/{provider}/{external_id}", response_model=UserDetail)
|
|
721
|
+
async def unlink_oauth_account(
|
|
722
|
+
user_id: str,
|
|
723
|
+
provider: str,
|
|
724
|
+
external_id: str,
|
|
725
|
+
auth: AuthContext = Depends(get_auth_context),
|
|
726
|
+
auth_service: APIAuthService = Depends(get_auth_service),
|
|
727
|
+
) -> UserDetail:
|
|
728
|
+
"""
|
|
729
|
+
Remove a linked OAuth identity from a user.
|
|
730
|
+
|
|
731
|
+
Users can unlink accounts from themselves without special permissions.
|
|
732
|
+
Only SYSTEM_ADMIN can unlink accounts from other users.
|
|
733
|
+
"""
|
|
734
|
+
# Check permissions: users can unlink from their own account, SYSTEM_ADMIN can unlink from any
|
|
735
|
+
if user_id != auth.user_id:
|
|
736
|
+
await check_permissions([PERMISSION_USERS_WRITE])(auth, auth_service)
|
|
737
|
+
|
|
738
|
+
user = await auth_service.unlink_user_oauth(user_id, provider, external_id)
|
|
739
|
+
if not user:
|
|
740
|
+
raise HTTPException(status_code=404, detail=ERROR_USER_NOT_FOUND)
|
|
741
|
+
|
|
742
|
+
return _build_user_detail(user_id, user, auth_service)
|
|
743
|
+
|
|
744
|
+
|
|
745
|
+
@router.post("/{user_id}/mint-wa", response_model=UserDetail)
|
|
746
|
+
async def mint_wise_authority(
|
|
747
|
+
user_id: str,
|
|
748
|
+
request: MintWARequest,
|
|
749
|
+
auth: AuthContext = Depends(get_auth_context),
|
|
750
|
+
auth_service: APIAuthService = Depends(get_auth_service),
|
|
751
|
+
) -> UserDetail:
|
|
752
|
+
"""
|
|
753
|
+
Mint a user as a Wise Authority.
|
|
754
|
+
|
|
755
|
+
Requires: ADMIN role or higher and valid Ed25519 signature from ROOT private key.
|
|
756
|
+
|
|
757
|
+
The signature should be over the message:
|
|
758
|
+
"MINT_WA:{user_id}:{wa_role}:{timestamp}"
|
|
759
|
+
|
|
760
|
+
If no signature is provided and private_key_path is specified, will attempt
|
|
761
|
+
to sign automatically using the key at that path.
|
|
762
|
+
"""
|
|
763
|
+
# Check if user is ADMIN or higher
|
|
764
|
+
if auth.role.level < APIRole.ADMIN.level:
|
|
765
|
+
raise HTTPException(status_code=403, detail=ERROR_ONLY_ADMIN_MINT_WA)
|
|
766
|
+
|
|
767
|
+
# Validate that request.wa_role is not ROOT
|
|
768
|
+
if request.wa_role == WARole.ROOT:
|
|
769
|
+
raise HTTPException(status_code=400, detail=ERROR_CANNOT_MINT_ROOT)
|
|
770
|
+
|
|
771
|
+
# If no signature provided but private key path is given, try to auto-sign
|
|
772
|
+
signature = request.signature
|
|
773
|
+
if not signature and request.private_key_path:
|
|
774
|
+
# Auto-sign using the provided private key
|
|
775
|
+
import base64
|
|
776
|
+
import os
|
|
777
|
+
|
|
778
|
+
from cryptography.hazmat.primitives.asymmetric import ed25519
|
|
779
|
+
|
|
780
|
+
try:
|
|
781
|
+
# Security: Validate the private key path
|
|
782
|
+
# Only allow alphanumeric characters, dots, dashes, and underscores in filename
|
|
783
|
+
import re
|
|
784
|
+
|
|
785
|
+
if not request.private_key_path:
|
|
786
|
+
raise HTTPException(status_code=400, detail="Private key path is required")
|
|
787
|
+
|
|
788
|
+
# Extract just the filename, no path components allowed
|
|
789
|
+
filename = os.path.basename(request.private_key_path)
|
|
790
|
+
if not re.match(r"^[a-zA-Z0-9._-]+$", filename):
|
|
791
|
+
raise HTTPException(
|
|
792
|
+
status_code=400,
|
|
793
|
+
detail="Invalid key filename. Only alphanumeric characters, dots, dashes, and underscores are allowed",
|
|
794
|
+
)
|
|
795
|
+
|
|
796
|
+
# Construct the safe path - no user input in directory path
|
|
797
|
+
allowed_base = os.path.expanduser("~/.ciris/wa_keys/")
|
|
798
|
+
safe_path = os.path.join(allowed_base, filename)
|
|
799
|
+
|
|
800
|
+
# Double-check with realpath to prevent any symlink attacks
|
|
801
|
+
resolved_path = os.path.realpath(safe_path)
|
|
802
|
+
allowed_base_resolved = os.path.realpath(allowed_base)
|
|
803
|
+
if not resolved_path.startswith(allowed_base_resolved):
|
|
804
|
+
raise HTTPException(status_code=403, detail="Access denied: path traversal detected")
|
|
805
|
+
|
|
806
|
+
# Read the private key
|
|
807
|
+
async with aiofiles.open(resolved_path, "rb") as f:
|
|
808
|
+
private_key_bytes = await f.read()
|
|
809
|
+
|
|
810
|
+
# Create Ed25519 private key object
|
|
811
|
+
private_key = ed25519.Ed25519PrivateKey.from_private_bytes(private_key_bytes)
|
|
812
|
+
|
|
813
|
+
# Sign the message
|
|
814
|
+
message = f"MINT_WA:{user_id}:{request.wa_role.value}"
|
|
815
|
+
signature_bytes = private_key.sign(message.encode())
|
|
816
|
+
|
|
817
|
+
# Encode to base64
|
|
818
|
+
signature = base64.b64encode(signature_bytes).decode()
|
|
819
|
+
|
|
820
|
+
logger.info(f"Auto-signed WA mint request for {user_id} using key file: {filename}")
|
|
821
|
+
|
|
822
|
+
except FileNotFoundError:
|
|
823
|
+
raise HTTPException(status_code=404, detail=f"Private key file not found: {filename}")
|
|
824
|
+
except Exception as e:
|
|
825
|
+
logger.error(f"Failed to auto-sign: {e}")
|
|
826
|
+
raise HTTPException(status_code=500, detail="Failed to auto-sign with provided private key")
|
|
827
|
+
|
|
828
|
+
if not signature:
|
|
829
|
+
raise HTTPException(status_code=400, detail=ERROR_SIGNATURE_OR_KEY_REQUIRED)
|
|
830
|
+
|
|
831
|
+
# Verify the ROOT signature
|
|
832
|
+
verified = await auth_service.verify_root_signature(user_id=user_id, wa_role=request.wa_role, signature=signature)
|
|
833
|
+
|
|
834
|
+
if not verified:
|
|
835
|
+
raise HTTPException(status_code=401, detail=ERROR_INVALID_SIGNATURE)
|
|
836
|
+
|
|
837
|
+
# Mint the user as WA
|
|
838
|
+
user = await auth_service.mint_wise_authority(user_id=user_id, wa_role=request.wa_role, minted_by=auth.user_id)
|
|
839
|
+
|
|
840
|
+
if not user:
|
|
841
|
+
raise HTTPException(status_code=404, detail=ERROR_USER_NOT_FOUND)
|
|
842
|
+
|
|
843
|
+
# Return updated user details
|
|
844
|
+
return await get_user(user_id, auth, auth_service, None)
|
|
845
|
+
|
|
846
|
+
|
|
847
|
+
@router.get("/wa/key-check", response_model=WAKeyCheckResponse)
|
|
848
|
+
async def check_wa_key_exists(
|
|
849
|
+
path: str = Query(..., description="Filename of private key to check"),
|
|
850
|
+
auth: AuthContext = Depends(get_auth_context),
|
|
851
|
+
_: None = Depends(check_permissions([PERMISSION_WA_MINT])), # SYSTEM_ADMIN only
|
|
852
|
+
) -> WAKeyCheckResponse:
|
|
853
|
+
"""
|
|
854
|
+
Check if a WA private key exists at the given filename.
|
|
855
|
+
|
|
856
|
+
Requires: wa.mint permission (SYSTEM_ADMIN only)
|
|
857
|
+
|
|
858
|
+
This is used by the UI to determine if auto-signing is available.
|
|
859
|
+
Only checks files within ~/.ciris/wa_keys/ for security.
|
|
860
|
+
"""
|
|
861
|
+
import os
|
|
862
|
+
import re
|
|
863
|
+
|
|
864
|
+
try:
|
|
865
|
+
# Security: Validate the filename
|
|
866
|
+
# Extract just the filename, no path components allowed
|
|
867
|
+
filename = os.path.basename(path)
|
|
868
|
+
if not re.match(r"^[a-zA-Z0-9._-]+$", filename):
|
|
869
|
+
return WAKeyCheckResponse(
|
|
870
|
+
exists=False,
|
|
871
|
+
filename=filename,
|
|
872
|
+
error="Invalid filename. Only alphanumeric characters, dots, dashes, and underscores are allowed",
|
|
873
|
+
)
|
|
874
|
+
|
|
875
|
+
# Construct the safe path - no user input in directory path
|
|
876
|
+
allowed_base = os.path.expanduser("~/.ciris/wa_keys/")
|
|
877
|
+
safe_path = os.path.join(allowed_base, filename)
|
|
878
|
+
|
|
879
|
+
# Double-check with realpath to prevent any symlink attacks
|
|
880
|
+
resolved_path = os.path.realpath(safe_path)
|
|
881
|
+
allowed_base_resolved = os.path.realpath(allowed_base)
|
|
882
|
+
if not resolved_path.startswith(allowed_base_resolved):
|
|
883
|
+
return WAKeyCheckResponse(exists=False, filename=filename, error="Access denied: path traversal detected")
|
|
884
|
+
|
|
885
|
+
# Check if file exists and is readable
|
|
886
|
+
exists = os.path.isfile(resolved_path) and os.access(resolved_path, os.R_OK)
|
|
887
|
+
|
|
888
|
+
# If exists, check if it's a valid key size (32 bytes for Ed25519)
|
|
889
|
+
if exists:
|
|
890
|
+
file_size = os.path.getsize(resolved_path)
|
|
891
|
+
valid_size = file_size == 32 # Ed25519 private key is 32 bytes
|
|
892
|
+
|
|
893
|
+
return WAKeyCheckResponse(exists=True, valid_size=valid_size, size=file_size, filename=filename)
|
|
894
|
+
else:
|
|
895
|
+
return WAKeyCheckResponse(exists=False, filename=filename)
|
|
896
|
+
|
|
897
|
+
except Exception as e:
|
|
898
|
+
logger.error(f"Error checking key file {filename}: {e}")
|
|
899
|
+
return WAKeyCheckResponse(exists=False, error="Failed to check key file", filename=filename)
|
|
900
|
+
|
|
901
|
+
|
|
902
|
+
@router.delete("/{user_id}", response_model=DeactivateUserResponse)
|
|
903
|
+
async def deactivate_user(
|
|
904
|
+
user_id: str,
|
|
905
|
+
auth: AuthContext = Depends(get_auth_context),
|
|
906
|
+
auth_service: APIAuthService = Depends(get_auth_service),
|
|
907
|
+
_: None = Depends(check_permissions([PERMISSION_USERS_DELETE])),
|
|
908
|
+
) -> DeactivateUserResponse:
|
|
909
|
+
"""
|
|
910
|
+
Deactivate a user account.
|
|
911
|
+
|
|
912
|
+
Requires: users.delete permission (SYSTEM_ADMIN only)
|
|
913
|
+
"""
|
|
914
|
+
# Prevent self-deactivation
|
|
915
|
+
if user_id == auth.user_id:
|
|
916
|
+
raise HTTPException(status_code=400, detail=ERROR_CANNOT_DEACTIVATE_SELF)
|
|
917
|
+
|
|
918
|
+
success = await auth_service.deactivate_user(user_id)
|
|
919
|
+
|
|
920
|
+
if not success:
|
|
921
|
+
raise HTTPException(status_code=404, detail=ERROR_USER_NOT_FOUND)
|
|
922
|
+
|
|
923
|
+
return DeactivateUserResponse(message="User deactivated successfully")
|
|
924
|
+
|
|
925
|
+
|
|
926
|
+
@router.get("/{user_id}/api-keys", response_model=List[APIKeyInfo])
|
|
927
|
+
async def list_user_api_keys(
|
|
928
|
+
user_id: str,
|
|
929
|
+
auth: AuthContext = Depends(get_auth_context),
|
|
930
|
+
auth_service: APIAuthService = Depends(get_auth_service),
|
|
931
|
+
) -> List[APIKeyInfo]:
|
|
932
|
+
"""
|
|
933
|
+
List API keys for a user.
|
|
934
|
+
|
|
935
|
+
Users can view their own keys.
|
|
936
|
+
ADMIN+ can view any user's keys.
|
|
937
|
+
"""
|
|
938
|
+
# Check permissions
|
|
939
|
+
if user_id != auth.user_id:
|
|
940
|
+
await check_permissions([PERMISSION_USERS_READ])(auth, auth_service)
|
|
941
|
+
|
|
942
|
+
keys = auth_service.list_user_api_keys(user_id)
|
|
943
|
+
|
|
944
|
+
# Mask the actual key values for security
|
|
945
|
+
return [
|
|
946
|
+
APIKeyInfo(
|
|
947
|
+
key_id=key.key_id,
|
|
948
|
+
masked_key=key.key_value[:8] + "****" if len(key.key_value) > 8 else "****",
|
|
949
|
+
created_at=key.created_at,
|
|
950
|
+
last_used=key.last_used,
|
|
951
|
+
is_active=key.is_active,
|
|
952
|
+
name=getattr(key, "name", None),
|
|
953
|
+
)
|
|
954
|
+
for key in keys
|
|
955
|
+
]
|
|
956
|
+
|
|
957
|
+
|
|
958
|
+
@router.put("/{user_id}/permissions", response_model=UserDetail)
|
|
959
|
+
async def update_user_permissions(
|
|
960
|
+
user_id: str,
|
|
961
|
+
request: UpdatePermissionsRequest,
|
|
962
|
+
auth: AuthContext = Depends(get_auth_context),
|
|
963
|
+
auth_service: APIAuthService = Depends(get_auth_service),
|
|
964
|
+
_: None = Depends(check_permissions([PERMISSION_MANAGE_USER_PERMISSIONS])),
|
|
965
|
+
) -> UserDetail:
|
|
966
|
+
"""
|
|
967
|
+
Update user's custom permissions.
|
|
968
|
+
|
|
969
|
+
Requires: users.permissions permission (AUTHORITY or higher)
|
|
970
|
+
|
|
971
|
+
This allows granting specific permissions to users beyond their role defaults.
|
|
972
|
+
For example, granting SEND_MESSAGES permission to an OBSERVER.
|
|
973
|
+
"""
|
|
974
|
+
# Update user permissions
|
|
975
|
+
user = await auth_service.update_user_permissions(user_id=user_id, permissions=request.permissions)
|
|
976
|
+
|
|
977
|
+
if not user:
|
|
978
|
+
raise HTTPException(status_code=404, detail=ERROR_USER_NOT_FOUND)
|
|
979
|
+
|
|
980
|
+
# Return updated user details
|
|
981
|
+
return await get_user(user_id, auth, auth_service, None)
|