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,1675 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Consolidated Graph Audit Service
|
|
3
|
+
|
|
4
|
+
Combines functionality from:
|
|
5
|
+
- AuditService (file-based)
|
|
6
|
+
- SignedAuditService (cryptographic signatures)
|
|
7
|
+
- GraphAuditService (graph-based storage)
|
|
8
|
+
|
|
9
|
+
This service provides:
|
|
10
|
+
1. Graph-based storage (everything is memory)
|
|
11
|
+
2. Optional file export for compliance
|
|
12
|
+
3. Cryptographic hash chain for tamper evidence
|
|
13
|
+
4. Unified interface for all audit operations
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import asyncio
|
|
17
|
+
import json
|
|
18
|
+
import logging
|
|
19
|
+
import sqlite3
|
|
20
|
+
import sys
|
|
21
|
+
from datetime import datetime
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
|
|
24
|
+
from uuid import uuid4
|
|
25
|
+
|
|
26
|
+
from ciris_engine.logic.persistence.db.dialect import get_adapter
|
|
27
|
+
from ciris_engine.logic.persistence.db.query_builder import ConflictResolution
|
|
28
|
+
from ciris_engine.logic.utils.jsondict_helpers import get_int, get_str
|
|
29
|
+
from ciris_engine.schemas.types import JSONDict
|
|
30
|
+
|
|
31
|
+
# Optional import for psutil
|
|
32
|
+
try:
|
|
33
|
+
import psutil
|
|
34
|
+
|
|
35
|
+
PSUTIL_AVAILABLE = True
|
|
36
|
+
except ImportError:
|
|
37
|
+
psutil = None # type: ignore
|
|
38
|
+
PSUTIL_AVAILABLE = False
|
|
39
|
+
|
|
40
|
+
# SQLite PRAGMA constants (avoid duplicate literals)
|
|
41
|
+
PRAGMA_JOURNAL_MODE_WAL = "PRAGMA journal_mode=WAL"
|
|
42
|
+
PRAGMA_BUSY_TIMEOUT_5000 = "PRAGMA busy_timeout=5000"
|
|
43
|
+
PRAGMA_SYNCHRONOUS_NORMAL = "PRAGMA synchronous=NORMAL"
|
|
44
|
+
|
|
45
|
+
if TYPE_CHECKING:
|
|
46
|
+
from ciris_engine.logic.registries.base import ServiceRegistry
|
|
47
|
+
from ciris_engine.schemas.audit.core import EventPayload, AuditLogEntry
|
|
48
|
+
|
|
49
|
+
from ciris_engine.constants import UTC_TIMEZONE_SUFFIX
|
|
50
|
+
from ciris_engine.logic.audit.hash_chain import AuditHashChain
|
|
51
|
+
from ciris_engine.logic.audit.verifier import AuditVerifier
|
|
52
|
+
from ciris_engine.logic.buses.memory_bus import MemoryBus
|
|
53
|
+
from ciris_engine.logic.services.base_graph_service import BaseGraphService
|
|
54
|
+
from ciris_engine.protocols.infrastructure.base import RegistryAwareServiceProtocol, ServiceRegistryProtocol
|
|
55
|
+
from ciris_engine.protocols.services import AuditService as AuditServiceProtocol
|
|
56
|
+
from ciris_engine.protocols.services.lifecycle.time import TimeServiceProtocol
|
|
57
|
+
from ciris_engine.schemas.audit.hash_chain import AuditEntryResult
|
|
58
|
+
from ciris_engine.schemas.runtime.audit import AuditActionContext, AuditRequest
|
|
59
|
+
from ciris_engine.schemas.runtime.enums import HandlerActionType, ServiceType
|
|
60
|
+
from ciris_engine.schemas.runtime.memory import TimeSeriesDataPoint
|
|
61
|
+
from ciris_engine.schemas.services.graph.audit import AuditEventData, AuditQuery, VerificationReport
|
|
62
|
+
|
|
63
|
+
# TSDB functionality integrated into graph nodes
|
|
64
|
+
from ciris_engine.schemas.services.graph_core import GraphNode, GraphScope, NodeType
|
|
65
|
+
from ciris_engine.schemas.services.nodes import AuditEntry as AuditEntryNode
|
|
66
|
+
from ciris_engine.schemas.services.nodes import AuditEntryContext
|
|
67
|
+
from ciris_engine.schemas.services.operations import MemoryOpStatus, MemoryQuery
|
|
68
|
+
|
|
69
|
+
# Type alias for protocol compatibility
|
|
70
|
+
AuditEntry = AuditEntryNode
|
|
71
|
+
|
|
72
|
+
logger = logging.getLogger(__name__)
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
from ciris_engine.logic.audit.signature_manager import AuditSignatureManager
|
|
76
|
+
except ImportError as e:
|
|
77
|
+
logger.error(f"Failed to import AuditSignatureManager: {e}")
|
|
78
|
+
raise
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class GraphAuditService(BaseGraphService, AuditServiceProtocol, RegistryAwareServiceProtocol):
|
|
82
|
+
"""
|
|
83
|
+
Consolidated audit service that stores all audit entries in the graph.
|
|
84
|
+
|
|
85
|
+
Features:
|
|
86
|
+
- Primary storage in graph (everything is memory)
|
|
87
|
+
- Optional file export for compliance
|
|
88
|
+
- Cryptographic hash chain for integrity
|
|
89
|
+
- Digital signatures for non-repudiation
|
|
90
|
+
- Unified interface for all audit operations
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
def __init__(
|
|
94
|
+
self,
|
|
95
|
+
memory_bus: Optional[MemoryBus] = None,
|
|
96
|
+
time_service: Optional[TimeServiceProtocol] = None,
|
|
97
|
+
# File export options
|
|
98
|
+
export_path: Optional[str] = None,
|
|
99
|
+
export_format: str = "jsonl", # jsonl, csv, or sqlite
|
|
100
|
+
# Hash chain options
|
|
101
|
+
enable_hash_chain: bool = True,
|
|
102
|
+
db_path: str = "ciris_audit.db",
|
|
103
|
+
key_path: str = "audit_keys",
|
|
104
|
+
# Retention options
|
|
105
|
+
retention_days: int = 90,
|
|
106
|
+
cache_size: int = 1000,
|
|
107
|
+
) -> None:
|
|
108
|
+
"""
|
|
109
|
+
Initialize the consolidated audit service.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
memory_bus: Bus for graph storage operations
|
|
113
|
+
time_service: Time service for consistent timestamps
|
|
114
|
+
export_path: Optional path for file exports
|
|
115
|
+
export_format: Format for exports (jsonl, csv, sqlite)
|
|
116
|
+
enable_hash_chain: Whether to maintain cryptographic hash chain
|
|
117
|
+
db_path: Path for hash chain database
|
|
118
|
+
key_path: Directory for signing keys
|
|
119
|
+
retention_days: How long to retain audit data
|
|
120
|
+
cache_size: Size of in-memory cache
|
|
121
|
+
"""
|
|
122
|
+
if not time_service:
|
|
123
|
+
raise RuntimeError("CRITICAL: TimeService is required for GraphAuditService")
|
|
124
|
+
|
|
125
|
+
# Initialize BaseGraphService with version 2.0.0
|
|
126
|
+
super().__init__(memory_bus=memory_bus, time_service=time_service, version="2.0.0")
|
|
127
|
+
|
|
128
|
+
self._service_registry: Optional[ServiceRegistryProtocol] = None
|
|
129
|
+
|
|
130
|
+
# Export configuration
|
|
131
|
+
self.export_path = Path(export_path) if export_path else None
|
|
132
|
+
self.export_format = export_format
|
|
133
|
+
|
|
134
|
+
# Hash chain configuration
|
|
135
|
+
self.enable_hash_chain = enable_hash_chain
|
|
136
|
+
# For PostgreSQL, keep connection string as-is; for SQLite, ensure Path object
|
|
137
|
+
self.db_path: str | Path
|
|
138
|
+
if db_path.startswith(("postgresql://", "postgres://")):
|
|
139
|
+
self.db_path = db_path
|
|
140
|
+
else:
|
|
141
|
+
self.db_path = Path(db_path)
|
|
142
|
+
self.key_path = Path(key_path)
|
|
143
|
+
|
|
144
|
+
# Retention configuration
|
|
145
|
+
self.retention_days = retention_days
|
|
146
|
+
|
|
147
|
+
# Cache for recent entries
|
|
148
|
+
self._recent_entries: List[AuditRequest] = []
|
|
149
|
+
self._max_cached_entries = cache_size
|
|
150
|
+
|
|
151
|
+
# Hash chain components
|
|
152
|
+
self.hash_chain: Optional[AuditHashChain] = None
|
|
153
|
+
self.signature_manager: Optional[AuditSignatureManager] = None
|
|
154
|
+
self.verifier: Optional[AuditVerifier] = None
|
|
155
|
+
self._db_connection: Optional[sqlite3.Connection] = None
|
|
156
|
+
|
|
157
|
+
# Export buffer
|
|
158
|
+
self._export_buffer: List[AuditRequest] = []
|
|
159
|
+
self._export_task: Optional[asyncio.Task[None]] = None
|
|
160
|
+
|
|
161
|
+
# Memory tracking
|
|
162
|
+
self._process = psutil.Process() if PSUTIL_AVAILABLE else None
|
|
163
|
+
|
|
164
|
+
# Track uptime
|
|
165
|
+
self._start_time: Optional[datetime] = None
|
|
166
|
+
|
|
167
|
+
# Lock for hash chain operations
|
|
168
|
+
self._hash_chain_lock = asyncio.Lock()
|
|
169
|
+
|
|
170
|
+
async def attach_registry(self, registry: "ServiceRegistryProtocol") -> None:
|
|
171
|
+
"""
|
|
172
|
+
Attach service registry for bus and service discovery.
|
|
173
|
+
|
|
174
|
+
Implements RegistryAwareServiceProtocol to enable proper initialization
|
|
175
|
+
of memory bus dependency.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
registry: Service registry providing access to buses and services
|
|
179
|
+
"""
|
|
180
|
+
self._service_registry = registry
|
|
181
|
+
|
|
182
|
+
if not self._memory_bus and self._service_registry and self._time_service:
|
|
183
|
+
try:
|
|
184
|
+
from ciris_engine.logic.buses import MemoryBus
|
|
185
|
+
|
|
186
|
+
self._memory_bus = MemoryBus(self._service_registry, self._time_service)
|
|
187
|
+
except Exception as e:
|
|
188
|
+
logger.error(f"Failed to initialize memory bus: {e}")
|
|
189
|
+
|
|
190
|
+
async def start(self) -> None:
|
|
191
|
+
"""Start the audit service."""
|
|
192
|
+
# Don't call super() as BaseService has async start
|
|
193
|
+
self._started = True
|
|
194
|
+
|
|
195
|
+
logger.info("Starting consolidated GraphAuditService")
|
|
196
|
+
|
|
197
|
+
# Set start time
|
|
198
|
+
if self._time_service:
|
|
199
|
+
self._start_time = self._time_service.now()
|
|
200
|
+
else:
|
|
201
|
+
self._start_time = datetime.now()
|
|
202
|
+
|
|
203
|
+
# Initialize hash chain if enabled
|
|
204
|
+
if self.enable_hash_chain:
|
|
205
|
+
await self._initialize_hash_chain()
|
|
206
|
+
|
|
207
|
+
# Create export directory if needed
|
|
208
|
+
if self.export_path:
|
|
209
|
+
self.export_path.parent.mkdir(parents=True, exist_ok=True)
|
|
210
|
+
|
|
211
|
+
# Start export task if configured
|
|
212
|
+
if self.export_path:
|
|
213
|
+
self._export_task = asyncio.create_task(self._export_worker())
|
|
214
|
+
|
|
215
|
+
logger.info("GraphAuditService started - all audit data flows through graph")
|
|
216
|
+
|
|
217
|
+
async def stop(self) -> None:
|
|
218
|
+
"""Stop the audit service."""
|
|
219
|
+
# Flush export buffer
|
|
220
|
+
if self._export_buffer:
|
|
221
|
+
await self._flush_exports()
|
|
222
|
+
|
|
223
|
+
# Cancel export task
|
|
224
|
+
if self._export_task:
|
|
225
|
+
self._export_task.cancel()
|
|
226
|
+
try:
|
|
227
|
+
await self._export_task
|
|
228
|
+
except asyncio.CancelledError:
|
|
229
|
+
# Re-raise after cleanup as per asyncio best practices
|
|
230
|
+
raise
|
|
231
|
+
|
|
232
|
+
# Log final shutdown event BEFORE closing database
|
|
233
|
+
from ciris_engine.schemas.audit.core import EventPayload
|
|
234
|
+
|
|
235
|
+
shutdown_event = EventPayload(
|
|
236
|
+
action="shutdown",
|
|
237
|
+
service_name="audit_service",
|
|
238
|
+
user_id="system",
|
|
239
|
+
result="success",
|
|
240
|
+
)
|
|
241
|
+
try:
|
|
242
|
+
await self.log_event("audit_service_shutdown", shutdown_event)
|
|
243
|
+
except Exception as e:
|
|
244
|
+
logger.warning(f"Failed to log shutdown event: {e}")
|
|
245
|
+
|
|
246
|
+
# Close database connection AFTER logging
|
|
247
|
+
if self._db_connection:
|
|
248
|
+
self._db_connection.close()
|
|
249
|
+
|
|
250
|
+
logger.info("GraphAuditService stopped")
|
|
251
|
+
|
|
252
|
+
# Don't call super() as BaseService has async stop
|
|
253
|
+
self._started = False
|
|
254
|
+
|
|
255
|
+
async def log_action(
|
|
256
|
+
self, action_type: HandlerActionType, context: AuditActionContext, outcome: Optional[str] = None
|
|
257
|
+
) -> AuditEntryResult:
|
|
258
|
+
"""Log an action and return audit entry with hash chain data (REQUIRED)."""
|
|
259
|
+
# Create audit entry
|
|
260
|
+
import json
|
|
261
|
+
|
|
262
|
+
from ciris_engine.schemas.audit.hash_chain import AuditEntryResult
|
|
263
|
+
|
|
264
|
+
logger.info(
|
|
265
|
+
f"DEBUG: log_action called with action_type={action_type.value}, context.parameters={context.parameters}"
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
# Serialize parameters dict to JSON string for AuditRequest (Dict[str, str] requirement)
|
|
269
|
+
parameters_json = json.dumps(context.parameters) if context.parameters else "{}"
|
|
270
|
+
|
|
271
|
+
entry = AuditRequest(
|
|
272
|
+
entry_id=str(uuid4()),
|
|
273
|
+
timestamp=self._time_service.now() if self._time_service else datetime.now(),
|
|
274
|
+
entity_id=context.thought_id,
|
|
275
|
+
event_type=action_type.value,
|
|
276
|
+
actor=context.handler_name or "system",
|
|
277
|
+
details={
|
|
278
|
+
"action_type": action_type.value,
|
|
279
|
+
"thought_id": context.thought_id,
|
|
280
|
+
"task_id": context.task_id,
|
|
281
|
+
"handler_name": context.handler_name,
|
|
282
|
+
"metadata": str(getattr(context, "metadata", {})),
|
|
283
|
+
"parameters": parameters_json, # JSON-serialized parameters (e.g., {"tool_name": "reddit_submit_post"})
|
|
284
|
+
},
|
|
285
|
+
outcome=outcome,
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
logger.debug(f"Created AuditRequest with details.parameters={entry.details.get('parameters')}")
|
|
289
|
+
|
|
290
|
+
# Add to hash chain FIRST (REQUIRED in production)
|
|
291
|
+
hash_chain_data = await self._add_to_hash_chain(entry)
|
|
292
|
+
|
|
293
|
+
if not hash_chain_data:
|
|
294
|
+
raise RuntimeError(
|
|
295
|
+
f"Hash chain data not generated for action {action_type.value}. "
|
|
296
|
+
f"enable_hash_chain={self.enable_hash_chain}. "
|
|
297
|
+
f"This is a critical audit trail failure."
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
# Store in graph WITH hash chain data (signature + entry_hash)
|
|
301
|
+
await self._store_entry_in_graph(entry, action_type, hash_chain_data)
|
|
302
|
+
|
|
303
|
+
# Cache for quick access
|
|
304
|
+
self._cache_entry(entry)
|
|
305
|
+
|
|
306
|
+
# Queue for export if configured
|
|
307
|
+
if self.export_path:
|
|
308
|
+
self._export_buffer.append(entry)
|
|
309
|
+
|
|
310
|
+
# Return audit entry result with REQUIRED fields
|
|
311
|
+
return AuditEntryResult(
|
|
312
|
+
entry_id=entry.entry_id,
|
|
313
|
+
sequence_number=hash_chain_data["sequence_number"],
|
|
314
|
+
entry_hash=hash_chain_data["entry_hash"],
|
|
315
|
+
previous_hash=hash_chain_data.get("previous_hash"),
|
|
316
|
+
signature=hash_chain_data["signature"],
|
|
317
|
+
signing_key_id=hash_chain_data.get("signing_key_id"),
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
async def log_event(self, event_type: str, event_data: "EventPayload", **kwargs: object) -> AuditEntryResult:
|
|
321
|
+
"""Log a general event.
|
|
322
|
+
|
|
323
|
+
Args:
|
|
324
|
+
event_type: Type of event being logged
|
|
325
|
+
event_data: Event data as EventPayload object
|
|
326
|
+
|
|
327
|
+
Returns:
|
|
328
|
+
AuditEntryResult with entry_id and hash chain data (if enabled)
|
|
329
|
+
"""
|
|
330
|
+
|
|
331
|
+
# Convert EventPayload to AuditEventData
|
|
332
|
+
audit_data = AuditEventData(
|
|
333
|
+
entity_id=str(getattr(event_data, "user_id", "unknown")),
|
|
334
|
+
actor=str(getattr(event_data, "service_name", "system")),
|
|
335
|
+
outcome=str(getattr(event_data, "result", "success")),
|
|
336
|
+
severity="info",
|
|
337
|
+
action=str(getattr(event_data, "action", event_type)),
|
|
338
|
+
resource=str(getattr(event_data, "service_name", event_type)),
|
|
339
|
+
reason=str(getattr(event_data, "error", "event_logged") or "event_logged"),
|
|
340
|
+
metadata={},
|
|
341
|
+
)
|
|
342
|
+
try:
|
|
343
|
+
# Create audit entry with string-only details
|
|
344
|
+
details_dict = {}
|
|
345
|
+
for key, value in audit_data.model_dump().items():
|
|
346
|
+
if value is not None:
|
|
347
|
+
details_dict[key] = str(value) if not isinstance(value, str) else value
|
|
348
|
+
|
|
349
|
+
entry = AuditRequest(
|
|
350
|
+
entry_id=str(uuid4()),
|
|
351
|
+
timestamp=self._time_service.now() if self._time_service else datetime.now(),
|
|
352
|
+
entity_id=audit_data.entity_id,
|
|
353
|
+
event_type=event_type,
|
|
354
|
+
actor=audit_data.actor,
|
|
355
|
+
details=details_dict,
|
|
356
|
+
outcome=audit_data.outcome,
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
# Add to hash chain FIRST to get signature before storing in graph
|
|
360
|
+
logger.debug(f"enable_hash_chain={self.enable_hash_chain}")
|
|
361
|
+
hash_chain_data = None
|
|
362
|
+
if self.enable_hash_chain:
|
|
363
|
+
logger.debug("Adding entry to hash chain")
|
|
364
|
+
hash_chain_data = await self._add_to_hash_chain(entry)
|
|
365
|
+
else:
|
|
366
|
+
logger.debug("Hash chain disabled, not writing to audit_log table")
|
|
367
|
+
|
|
368
|
+
# Create graph node WITH signature from hash chain
|
|
369
|
+
node = AuditEntryNode(
|
|
370
|
+
id=f"audit_{entry.entry_id}",
|
|
371
|
+
action=event_type,
|
|
372
|
+
actor=entry.actor,
|
|
373
|
+
timestamp=entry.timestamp,
|
|
374
|
+
context=AuditEntryContext(
|
|
375
|
+
service_name=self.__class__.__name__,
|
|
376
|
+
correlation_id=entry.entry_id,
|
|
377
|
+
additional_data={
|
|
378
|
+
"event_type": event_type,
|
|
379
|
+
"severity": audit_data.severity,
|
|
380
|
+
"outcome": entry.outcome or "logged",
|
|
381
|
+
},
|
|
382
|
+
),
|
|
383
|
+
signature=hash_chain_data.get("signature") if hash_chain_data else None,
|
|
384
|
+
hash_chain=hash_chain_data.get("entry_hash") if hash_chain_data else None,
|
|
385
|
+
scope=GraphScope.LOCAL,
|
|
386
|
+
attributes={},
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
# Store in graph with signature already set
|
|
390
|
+
if self._memory_bus:
|
|
391
|
+
await self._memory_bus.memorize(
|
|
392
|
+
node=node.to_graph_node(),
|
|
393
|
+
handler_name="audit_service",
|
|
394
|
+
metadata={"audit_entry": entry.model_dump(), "event": True, "immutable": True},
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
# Cache and export
|
|
398
|
+
self._cache_entry(entry)
|
|
399
|
+
if self.export_path:
|
|
400
|
+
logger.debug(f"Adding to export buffer, path={self.export_path}")
|
|
401
|
+
self._export_buffer.append(entry)
|
|
402
|
+
else:
|
|
403
|
+
logger.debug("No export path configured")
|
|
404
|
+
|
|
405
|
+
# Create trace correlation for this event
|
|
406
|
+
from ciris_engine.schemas.runtime.enums import HandlerActionType
|
|
407
|
+
|
|
408
|
+
# Extract action type from event data - try to map to HandlerActionType
|
|
409
|
+
# For non-handler events like WA operations, system events, etc., default to OBSERVE
|
|
410
|
+
action_name = event_data.action if hasattr(event_data, "action") else event_type
|
|
411
|
+
try:
|
|
412
|
+
action_type = HandlerActionType(action_name)
|
|
413
|
+
except ValueError:
|
|
414
|
+
# Not a handler action - use OBSERVE as default for system/auth events
|
|
415
|
+
action_type = HandlerActionType.OBSERVE
|
|
416
|
+
|
|
417
|
+
await self._create_trace_correlation(entry, action_type)
|
|
418
|
+
|
|
419
|
+
# Return full audit entry result with hash chain data
|
|
420
|
+
return AuditEntryResult(
|
|
421
|
+
entry_id=entry.entry_id,
|
|
422
|
+
sequence_number=hash_chain_data.get("sequence_number") if hash_chain_data else None,
|
|
423
|
+
entry_hash=hash_chain_data.get("entry_hash") if hash_chain_data else None,
|
|
424
|
+
previous_hash=hash_chain_data.get("previous_hash") if hash_chain_data else None,
|
|
425
|
+
signature=hash_chain_data.get("signature") if hash_chain_data else None,
|
|
426
|
+
signing_key_id=hash_chain_data.get("signing_key_id") if hash_chain_data else None,
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
except Exception as e:
|
|
430
|
+
logger.error(f"Failed to log event {event_type}: {e}")
|
|
431
|
+
# Fail fast - audit failures are critical
|
|
432
|
+
raise RuntimeError(f"Failed to create audit entry for event {event_type}: {e}") from e
|
|
433
|
+
|
|
434
|
+
async def log_conscience_event(
|
|
435
|
+
self, thought_id: str, decision: str, reasoning: str, metadata: Optional["EventPayload"] = None
|
|
436
|
+
) -> None:
|
|
437
|
+
"""Log conscience check events."""
|
|
438
|
+
# Create EventPayload for log_event
|
|
439
|
+
from ciris_engine.schemas.audit.core import EventPayload
|
|
440
|
+
|
|
441
|
+
# Use metadata if provided, otherwise create basic payload
|
|
442
|
+
if metadata:
|
|
443
|
+
event_payload = metadata
|
|
444
|
+
else:
|
|
445
|
+
event_payload = EventPayload(
|
|
446
|
+
action="conscience_check",
|
|
447
|
+
service_name="conscience_system",
|
|
448
|
+
user_id=thought_id,
|
|
449
|
+
result="allowed" if decision == "ALLOW" else "denied",
|
|
450
|
+
error=reasoning if decision != "ALLOW" else None,
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
await self.log_event("conscience_check", event_payload)
|
|
454
|
+
|
|
455
|
+
async def get_audit_trail(
|
|
456
|
+
self, entity_id: Optional[str] = None, hours: int = 24, action_types: Optional[List[str]] = None
|
|
457
|
+
) -> List[AuditEntry]:
|
|
458
|
+
"""Get audit trail for an entity."""
|
|
459
|
+
# Check cache first if entity_id provided
|
|
460
|
+
cached = []
|
|
461
|
+
if entity_id:
|
|
462
|
+
cached = [e for e in self._recent_entries if e.entity_id == entity_id]
|
|
463
|
+
else:
|
|
464
|
+
cached = list(self._recent_entries)
|
|
465
|
+
|
|
466
|
+
# Query from graph
|
|
467
|
+
if not self._memory_bus:
|
|
468
|
+
logger.error("Memory bus not available for audit queries")
|
|
469
|
+
# Convert cached AuditRequest to AuditEntry
|
|
470
|
+
return [self._audit_request_to_entry(e) for e in cached]
|
|
471
|
+
|
|
472
|
+
try:
|
|
473
|
+
# Query timeseries data
|
|
474
|
+
timeseries_data = await self._memory_bus.recall_timeseries(
|
|
475
|
+
scope="local", hours=hours, correlation_types=["AUDIT_EVENT"], handler_name="audit_service"
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
# Convert to AuditEntry objects and filter
|
|
479
|
+
results = self._convert_timeseries_to_entries(timeseries_data, entity_id)
|
|
480
|
+
|
|
481
|
+
# Combine with cache and deduplicate
|
|
482
|
+
# Convert AuditRequest to AuditEntry if needed
|
|
483
|
+
all_entries: Dict[str, AuditEntry] = {}
|
|
484
|
+
for req in cached:
|
|
485
|
+
if hasattr(req, "entry_id"):
|
|
486
|
+
entry = self._audit_request_to_entry(req)
|
|
487
|
+
all_entries[entry.id] = entry
|
|
488
|
+
for result_entry in results:
|
|
489
|
+
if hasattr(result_entry, "id"):
|
|
490
|
+
all_entries[result_entry.id] = result_entry
|
|
491
|
+
elif hasattr(result_entry, "entry_id"):
|
|
492
|
+
# This shouldn't happen but handle it anyway
|
|
493
|
+
entry = self._audit_request_to_entry(result_entry) # type: ignore
|
|
494
|
+
all_entries[entry.id] = entry
|
|
495
|
+
|
|
496
|
+
# Sort and limit
|
|
497
|
+
sorted_entries = sorted(all_entries.values(), key=lambda x: x.timestamp, reverse=True)
|
|
498
|
+
|
|
499
|
+
# Apply action_types filter if provided
|
|
500
|
+
if action_types:
|
|
501
|
+
sorted_entries = [e for e in sorted_entries if hasattr(e, "action") and e.action in action_types]
|
|
502
|
+
|
|
503
|
+
return sorted_entries
|
|
504
|
+
|
|
505
|
+
except Exception as e:
|
|
506
|
+
logger.error(f"Failed to get audit trail: {e}")
|
|
507
|
+
# Convert cached AuditRequest to AuditEntry
|
|
508
|
+
return [self._audit_request_to_entry(e) for e in cached]
|
|
509
|
+
|
|
510
|
+
async def query_audit_trail(self, query: AuditQuery) -> List[AuditEntry]:
|
|
511
|
+
"""Query audit trail with advanced filters - implements AuditServiceProtocol."""
|
|
512
|
+
if not self._memory_bus:
|
|
513
|
+
return []
|
|
514
|
+
|
|
515
|
+
# Query audit_entry nodes directly from graph memory
|
|
516
|
+
from ciris_engine.schemas.services.graph_core import NodeType
|
|
517
|
+
|
|
518
|
+
# Search for all audit entries using query string format
|
|
519
|
+
# The search method looks for "type:" in the query string, not in filters
|
|
520
|
+
search_query = f"type:{NodeType.AUDIT_ENTRY.value} scope:{GraphScope.LOCAL.value}"
|
|
521
|
+
|
|
522
|
+
# Search for all audit entries
|
|
523
|
+
nodes = await self._memory_bus.search(search_query, filters=None, handler_name="audit_service")
|
|
524
|
+
|
|
525
|
+
# Convert GraphNode to AuditEntry
|
|
526
|
+
audit_entries = []
|
|
527
|
+
for node in nodes:
|
|
528
|
+
# Extract audit data from node attributes
|
|
529
|
+
if isinstance(node.attributes, dict):
|
|
530
|
+
attrs = node.attributes
|
|
531
|
+
elif hasattr(node.attributes, "model_dump"):
|
|
532
|
+
attrs = node.attributes.model_dump()
|
|
533
|
+
else:
|
|
534
|
+
continue
|
|
535
|
+
|
|
536
|
+
# Parse timestamp if it's a string
|
|
537
|
+
timestamp = attrs.get("timestamp", self._time_service.now() if self._time_service else datetime.now())
|
|
538
|
+
if isinstance(timestamp, str):
|
|
539
|
+
try:
|
|
540
|
+
timestamp = datetime.fromisoformat(timestamp.replace("Z", UTC_TIMEZONE_SUFFIX))
|
|
541
|
+
except (ValueError, TypeError):
|
|
542
|
+
timestamp = self._time_service.now() if self._time_service else datetime.now()
|
|
543
|
+
|
|
544
|
+
# Extract context data - handle both dict and nested structures
|
|
545
|
+
context_data = attrs.get("context", {})
|
|
546
|
+
if isinstance(context_data, dict):
|
|
547
|
+
# Extract service_name from nested structure or top level
|
|
548
|
+
service_name = context_data.get("service_name", attrs.get("service_name", ""))
|
|
549
|
+
correlation_id = context_data.get("correlation_id", attrs.get("correlation_id", ""))
|
|
550
|
+
|
|
551
|
+
# Get additional_data and flatten it to primitives only
|
|
552
|
+
additional_data = context_data.get("additional_data", {})
|
|
553
|
+
if isinstance(additional_data, dict):
|
|
554
|
+
# Filter out non-primitive values
|
|
555
|
+
flat_data: Dict[str, Union[str, int, float, bool]] = {}
|
|
556
|
+
for k, v in additional_data.items():
|
|
557
|
+
if isinstance(v, (str, int, float, bool)):
|
|
558
|
+
flat_data[k] = v
|
|
559
|
+
elif v is None:
|
|
560
|
+
# Skip None values
|
|
561
|
+
continue
|
|
562
|
+
else:
|
|
563
|
+
# Convert complex types to string
|
|
564
|
+
flat_data[k] = str(v)
|
|
565
|
+
additional_data = flat_data
|
|
566
|
+
else:
|
|
567
|
+
service_name = attrs.get("service_name", "")
|
|
568
|
+
correlation_id = attrs.get("correlation_id", "")
|
|
569
|
+
additional_data = {}
|
|
570
|
+
|
|
571
|
+
# Create AuditEntryNode from graph data
|
|
572
|
+
entry = AuditEntryNode(
|
|
573
|
+
id=node.id,
|
|
574
|
+
action=attrs.get("action", ""),
|
|
575
|
+
actor=attrs.get("actor", ""),
|
|
576
|
+
timestamp=timestamp,
|
|
577
|
+
context=AuditEntryContext(
|
|
578
|
+
service_name=service_name, correlation_id=correlation_id, additional_data=additional_data
|
|
579
|
+
),
|
|
580
|
+
signature=attrs.get("signature"),
|
|
581
|
+
hash_chain=attrs.get("hash_chain"),
|
|
582
|
+
scope=node.scope,
|
|
583
|
+
attributes={},
|
|
584
|
+
)
|
|
585
|
+
|
|
586
|
+
# Apply filters from query
|
|
587
|
+
if query.start_time and entry.timestamp < query.start_time:
|
|
588
|
+
continue
|
|
589
|
+
if query.end_time and entry.timestamp > query.end_time:
|
|
590
|
+
continue
|
|
591
|
+
if query.actor and entry.actor != query.actor:
|
|
592
|
+
continue
|
|
593
|
+
if query.event_type and entry.action != query.event_type:
|
|
594
|
+
continue
|
|
595
|
+
if query.entity_id and entry.context.correlation_id != query.entity_id:
|
|
596
|
+
continue
|
|
597
|
+
if query.search_text:
|
|
598
|
+
# Simple text search in action and actor
|
|
599
|
+
search_lower = query.search_text.lower()
|
|
600
|
+
if search_lower not in entry.action.lower() and search_lower not in entry.actor.lower():
|
|
601
|
+
continue
|
|
602
|
+
|
|
603
|
+
audit_entries.append(entry)
|
|
604
|
+
|
|
605
|
+
# Sort and paginate
|
|
606
|
+
audit_entries.sort(key=lambda e: e.timestamp, reverse=query.order_desc)
|
|
607
|
+
|
|
608
|
+
# Apply offset and limit
|
|
609
|
+
start = query.offset
|
|
610
|
+
end = query.offset + query.limit if query.limit else None
|
|
611
|
+
|
|
612
|
+
return audit_entries[start:end]
|
|
613
|
+
|
|
614
|
+
async def verify_audit_integrity(self) -> VerificationReport:
|
|
615
|
+
"""Verify the integrity of the audit trail."""
|
|
616
|
+
start_time = self._time_service.now() if self._time_service else datetime.now()
|
|
617
|
+
|
|
618
|
+
if not self.enable_hash_chain or not self.verifier:
|
|
619
|
+
return VerificationReport(
|
|
620
|
+
verified=False,
|
|
621
|
+
total_entries=0,
|
|
622
|
+
valid_entries=0,
|
|
623
|
+
invalid_entries=0,
|
|
624
|
+
chain_intact=False,
|
|
625
|
+
verification_started=start_time,
|
|
626
|
+
verification_completed=self._time_service.now() if self._time_service else datetime.now(),
|
|
627
|
+
duration_ms=0,
|
|
628
|
+
errors=["Hash chain not enabled"],
|
|
629
|
+
)
|
|
630
|
+
|
|
631
|
+
try:
|
|
632
|
+
result = await asyncio.to_thread(self.verifier.verify_complete_chain)
|
|
633
|
+
end_time = self._time_service.now() if self._time_service else datetime.now()
|
|
634
|
+
|
|
635
|
+
# Extract all errors
|
|
636
|
+
all_errors = []
|
|
637
|
+
all_errors.extend(result.hash_chain_errors or [])
|
|
638
|
+
all_errors.extend(result.signature_errors or [])
|
|
639
|
+
if result.error:
|
|
640
|
+
all_errors.append(result.error)
|
|
641
|
+
|
|
642
|
+
return VerificationReport(
|
|
643
|
+
verified=result.valid,
|
|
644
|
+
total_entries=result.entries_verified,
|
|
645
|
+
valid_entries=result.entries_verified if result.valid else 0,
|
|
646
|
+
invalid_entries=0 if result.valid else result.entries_verified,
|
|
647
|
+
chain_intact=result.hash_chain_valid,
|
|
648
|
+
last_valid_entry=None, # Not provided by CompleteVerificationResult
|
|
649
|
+
first_invalid_entry=None, # Not provided by CompleteVerificationResult
|
|
650
|
+
verification_started=start_time,
|
|
651
|
+
verification_completed=end_time,
|
|
652
|
+
duration_ms=(end_time - start_time).total_seconds() * 1000,
|
|
653
|
+
errors=all_errors,
|
|
654
|
+
warnings=[], # No warnings in CompleteVerificationResult
|
|
655
|
+
)
|
|
656
|
+
except Exception as e:
|
|
657
|
+
logger.error(f"Audit verification failed: {e}")
|
|
658
|
+
end_time = self._time_service.now() if self._time_service else datetime.now()
|
|
659
|
+
return VerificationReport(
|
|
660
|
+
verified=False,
|
|
661
|
+
total_entries=0,
|
|
662
|
+
valid_entries=0,
|
|
663
|
+
invalid_entries=0,
|
|
664
|
+
chain_intact=False,
|
|
665
|
+
verification_started=start_time,
|
|
666
|
+
verification_completed=end_time,
|
|
667
|
+
duration_ms=(end_time - start_time).total_seconds() * 1000,
|
|
668
|
+
errors=[str(e)],
|
|
669
|
+
)
|
|
670
|
+
|
|
671
|
+
async def get_verification_report(self) -> VerificationReport:
|
|
672
|
+
"""Generate a comprehensive audit verification report."""
|
|
673
|
+
start_time = self._time_service.now() if self._time_service else datetime.now()
|
|
674
|
+
|
|
675
|
+
if not self.enable_hash_chain or not self.verifier:
|
|
676
|
+
return VerificationReport(
|
|
677
|
+
verified=False,
|
|
678
|
+
total_entries=0,
|
|
679
|
+
valid_entries=0,
|
|
680
|
+
invalid_entries=0,
|
|
681
|
+
chain_intact=False,
|
|
682
|
+
verification_started=start_time,
|
|
683
|
+
verification_completed=self._time_service.now() if self._time_service else datetime.now(),
|
|
684
|
+
duration_ms=0,
|
|
685
|
+
errors=["Hash chain not enabled"],
|
|
686
|
+
)
|
|
687
|
+
|
|
688
|
+
try:
|
|
689
|
+
# Delegate to verify_audit_integrity which already returns VerificationReport
|
|
690
|
+
return await self.verify_audit_integrity()
|
|
691
|
+
except Exception as e:
|
|
692
|
+
logger.error(f"Failed to generate verification report: {e}")
|
|
693
|
+
end_time = self._time_service.now() if self._time_service else datetime.now()
|
|
694
|
+
return VerificationReport(
|
|
695
|
+
verified=False,
|
|
696
|
+
total_entries=0,
|
|
697
|
+
valid_entries=0,
|
|
698
|
+
invalid_entries=0,
|
|
699
|
+
chain_intact=False,
|
|
700
|
+
verification_started=start_time,
|
|
701
|
+
verification_completed=end_time,
|
|
702
|
+
duration_ms=(end_time - start_time).total_seconds() * 1000,
|
|
703
|
+
errors=[str(e)],
|
|
704
|
+
)
|
|
705
|
+
|
|
706
|
+
async def export_audit_data(
|
|
707
|
+
self, start_time: Optional[datetime] = None, end_time: Optional[datetime] = None, format: Optional[str] = None
|
|
708
|
+
) -> str:
|
|
709
|
+
"""Export audit data to file."""
|
|
710
|
+
format = format or self.export_format
|
|
711
|
+
|
|
712
|
+
# Query data
|
|
713
|
+
from ciris_engine.schemas.services.graph.audit import AuditQuery
|
|
714
|
+
|
|
715
|
+
query = AuditQuery(start_time=start_time, end_time=end_time, limit=10000) # Higher limit for exports
|
|
716
|
+
entries = await self.query_audit_trail(query)
|
|
717
|
+
|
|
718
|
+
# Generate filename
|
|
719
|
+
timestamp = (self._time_service.now() if self._time_service else datetime.now()).strftime("%Y%m%d_%H%M%S")
|
|
720
|
+
if not self.export_path:
|
|
721
|
+
raise ValueError("Export path not configured")
|
|
722
|
+
filename = self.export_path.parent / f"audit_export_{timestamp}.{format}"
|
|
723
|
+
|
|
724
|
+
# Convert AuditEntry to AuditRequest for export methods
|
|
725
|
+
audit_requests = []
|
|
726
|
+
for entry in entries:
|
|
727
|
+
audit_requests.append(
|
|
728
|
+
AuditRequest(
|
|
729
|
+
entry_id=entry.id,
|
|
730
|
+
timestamp=entry.timestamp,
|
|
731
|
+
entity_id=entry.context.correlation_id or entry.actor,
|
|
732
|
+
event_type=entry.action,
|
|
733
|
+
actor=entry.actor,
|
|
734
|
+
details={
|
|
735
|
+
"service": entry.context.service_name or "",
|
|
736
|
+
"method": entry.context.method_name or "",
|
|
737
|
+
"user_id": entry.context.user_id or "",
|
|
738
|
+
**(
|
|
739
|
+
{k: str(v) for k, v in entry.context.additional_data.items()}
|
|
740
|
+
if entry.context.additional_data
|
|
741
|
+
else {}
|
|
742
|
+
),
|
|
743
|
+
},
|
|
744
|
+
outcome=None, # AuditEntry doesn't have outcome field
|
|
745
|
+
)
|
|
746
|
+
)
|
|
747
|
+
|
|
748
|
+
# Export based on format
|
|
749
|
+
if format == "jsonl":
|
|
750
|
+
await self._export_jsonl(audit_requests, filename)
|
|
751
|
+
elif format == "csv":
|
|
752
|
+
await self._export_csv(audit_requests, filename)
|
|
753
|
+
elif format == "sqlite":
|
|
754
|
+
await self._export_sqlite(audit_requests, filename)
|
|
755
|
+
else:
|
|
756
|
+
raise ValueError(f"Unsupported export format: {format}")
|
|
757
|
+
|
|
758
|
+
return str(filename)
|
|
759
|
+
|
|
760
|
+
# ========== GraphServiceProtocol Implementation ==========
|
|
761
|
+
|
|
762
|
+
def get_node_type(self) -> str:
|
|
763
|
+
"""Get the type of nodes this service manages."""
|
|
764
|
+
return "AUDIT"
|
|
765
|
+
|
|
766
|
+
# ========== ServiceProtocol Implementation ==========
|
|
767
|
+
|
|
768
|
+
def _collect_custom_metrics(self) -> Dict[str, float]:
|
|
769
|
+
"""Collect audit-specific metrics."""
|
|
770
|
+
metrics = super()._collect_custom_metrics()
|
|
771
|
+
|
|
772
|
+
# Calculate cache size
|
|
773
|
+
cache_size_mb = 0.0
|
|
774
|
+
try:
|
|
775
|
+
cache_size = sys.getsizeof(self._recent_entries) + sys.getsizeof(self._export_buffer)
|
|
776
|
+
cache_size_mb = cache_size / 1024 / 1024
|
|
777
|
+
except Exception:
|
|
778
|
+
pass
|
|
779
|
+
|
|
780
|
+
# Add audit-specific metrics
|
|
781
|
+
metrics.update(
|
|
782
|
+
{
|
|
783
|
+
"cached_entries": float(len(self._recent_entries)),
|
|
784
|
+
"pending_exports": float(len(self._export_buffer)),
|
|
785
|
+
"hash_chain_enabled": float(self.enable_hash_chain),
|
|
786
|
+
"cache_size_mb": cache_size_mb,
|
|
787
|
+
}
|
|
788
|
+
)
|
|
789
|
+
|
|
790
|
+
return metrics
|
|
791
|
+
|
|
792
|
+
async def get_metrics(self) -> Dict[str, float]:
|
|
793
|
+
"""
|
|
794
|
+
Get all audit service metrics including base, custom, and v1.4.3 specific.
|
|
795
|
+
"""
|
|
796
|
+
# Get all base + custom metrics
|
|
797
|
+
metrics = self._collect_metrics()
|
|
798
|
+
# Count total events from cache and estimate from graph
|
|
799
|
+
total_events = len(self._recent_entries)
|
|
800
|
+
|
|
801
|
+
# Count events by severity from cached entries
|
|
802
|
+
severity_counts = {"info": 0, "low": 0, "medium": 0, "high": 0, "critical": 0}
|
|
803
|
+
compliance_checks = 0
|
|
804
|
+
|
|
805
|
+
# Analyze cached entries for severity and compliance
|
|
806
|
+
for entry in self._recent_entries:
|
|
807
|
+
# Extract severity from details or determine from event type
|
|
808
|
+
details = entry.details or {}
|
|
809
|
+
severity = details.get("severity", "info")
|
|
810
|
+
|
|
811
|
+
# Count by severity
|
|
812
|
+
if severity in severity_counts:
|
|
813
|
+
severity_counts[severity] += 1
|
|
814
|
+
else:
|
|
815
|
+
severity_counts["info"] += 1
|
|
816
|
+
|
|
817
|
+
# Count compliance-related events
|
|
818
|
+
event_type = entry.event_type.lower()
|
|
819
|
+
if any(keyword in event_type for keyword in ["compliance", "audit", "verify", "check", "integrity"]):
|
|
820
|
+
compliance_checks += 1
|
|
821
|
+
|
|
822
|
+
# Calculate uptime
|
|
823
|
+
uptime_seconds = 0.0
|
|
824
|
+
if self._start_time:
|
|
825
|
+
current_time = self._time_service.now() if self._time_service else datetime.now()
|
|
826
|
+
uptime_seconds = (current_time - self._start_time).total_seconds()
|
|
827
|
+
|
|
828
|
+
# Return exact metrics from v1.4.3 set
|
|
829
|
+
# Add v1.4.3 specific metrics
|
|
830
|
+
metrics.update(
|
|
831
|
+
{
|
|
832
|
+
"audit_events_total": float(total_events),
|
|
833
|
+
"audit_events_by_severity": float(sum(severity_counts.values())), # Flattened count
|
|
834
|
+
"audit_compliance_checks": float(compliance_checks),
|
|
835
|
+
"audit_uptime_seconds": uptime_seconds,
|
|
836
|
+
}
|
|
837
|
+
)
|
|
838
|
+
|
|
839
|
+
return metrics
|
|
840
|
+
|
|
841
|
+
# ========== Private Helper Methods ==========
|
|
842
|
+
|
|
843
|
+
async def _store_entry_in_graph(
|
|
844
|
+
self, entry: AuditRequest, action_type: HandlerActionType, hash_chain_data: Optional[JSONDict] = None
|
|
845
|
+
) -> None:
|
|
846
|
+
"""Store an audit entry in the graph and create a trace correlation.
|
|
847
|
+
|
|
848
|
+
Args:
|
|
849
|
+
entry: The audit request to store
|
|
850
|
+
action_type: The handler action type
|
|
851
|
+
hash_chain_data: Optional hash chain data with signature/entry_hash to include in node
|
|
852
|
+
"""
|
|
853
|
+
if not self._memory_bus:
|
|
854
|
+
logger.error("Memory bus not available for audit storage")
|
|
855
|
+
return
|
|
856
|
+
|
|
857
|
+
# Create specialized audit node WITH signature from hash chain
|
|
858
|
+
# Build additional_data with core fields plus any extra parameters from context
|
|
859
|
+
import json
|
|
860
|
+
|
|
861
|
+
additional_data = {
|
|
862
|
+
"thought_id": entry.details.get("thought_id", ""),
|
|
863
|
+
"task_id": entry.details.get("task_id", ""),
|
|
864
|
+
"outcome": entry.outcome or "success",
|
|
865
|
+
"severity": self._get_severity(action_type),
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
# Include any additional parameters from the audit context (e.g., tool_name, follow_up_thought_id)
|
|
869
|
+
logger.debug(f"entry.details keys: {entry.details.keys()}")
|
|
870
|
+
if "parameters" in entry.details and entry.details["parameters"]:
|
|
871
|
+
logger.debug(f"Found parameters in entry.details: {entry.details['parameters']}")
|
|
872
|
+
try:
|
|
873
|
+
# Deserialize JSON string back to dict
|
|
874
|
+
params_dict = json.loads(entry.details["parameters"])
|
|
875
|
+
additional_data.update(params_dict)
|
|
876
|
+
logger.debug(f"Updated additional_data: {additional_data}")
|
|
877
|
+
except json.JSONDecodeError as e:
|
|
878
|
+
logger.warning(f"Failed to parse parameters JSON: {e}")
|
|
879
|
+
else:
|
|
880
|
+
logger.debug("No 'parameters' key in entry.details or empty")
|
|
881
|
+
|
|
882
|
+
node = AuditEntryNode(
|
|
883
|
+
id=f"audit_{action_type.value}_{entry.entry_id}",
|
|
884
|
+
action=action_type.value,
|
|
885
|
+
actor=entry.actor,
|
|
886
|
+
timestamp=entry.timestamp,
|
|
887
|
+
context=AuditEntryContext(
|
|
888
|
+
service_name=entry.details.get("handler_name", ""),
|
|
889
|
+
correlation_id=entry.entry_id,
|
|
890
|
+
additional_data=additional_data,
|
|
891
|
+
),
|
|
892
|
+
signature=hash_chain_data.get("signature") if hash_chain_data else None,
|
|
893
|
+
hash_chain=hash_chain_data.get("entry_hash") if hash_chain_data else None,
|
|
894
|
+
scope=GraphScope.LOCAL,
|
|
895
|
+
attributes={"action_type": action_type.value, "event_id": entry.entry_id},
|
|
896
|
+
)
|
|
897
|
+
|
|
898
|
+
# Store via memory bus with signature already set
|
|
899
|
+
result = await self._memory_bus.memorize(
|
|
900
|
+
node=node.to_graph_node(),
|
|
901
|
+
handler_name="audit_service",
|
|
902
|
+
metadata={"audit_entry": entry.model_dump(), "immutable": True},
|
|
903
|
+
)
|
|
904
|
+
|
|
905
|
+
if result.status != MemoryOpStatus.OK:
|
|
906
|
+
logger.error(f"Failed to store audit entry in graph: {result}")
|
|
907
|
+
|
|
908
|
+
# Create a ServiceCorrelation for trace tracking
|
|
909
|
+
await self._create_trace_correlation(entry, action_type)
|
|
910
|
+
|
|
911
|
+
async def _create_trace_correlation(self, entry: AuditRequest, action_type: HandlerActionType) -> None:
|
|
912
|
+
"""Create a ServiceCorrelation for trace tracking."""
|
|
913
|
+
logger.debug(f"Creating trace correlation for audit event {entry.entry_id}")
|
|
914
|
+
try:
|
|
915
|
+
from ciris_engine.schemas.telemetry.core import (
|
|
916
|
+
CorrelationType,
|
|
917
|
+
ServiceCorrelation,
|
|
918
|
+
ServiceCorrelationStatus,
|
|
919
|
+
ServiceRequestData,
|
|
920
|
+
ServiceResponseData,
|
|
921
|
+
TraceContext,
|
|
922
|
+
)
|
|
923
|
+
|
|
924
|
+
# Get telemetry service from runtime
|
|
925
|
+
telemetry_service = None
|
|
926
|
+
if hasattr(self, "_runtime") and self._runtime:
|
|
927
|
+
telemetry_service = getattr(self._runtime, "telemetry_service", None)
|
|
928
|
+
|
|
929
|
+
if not telemetry_service:
|
|
930
|
+
# Try to get from service registry
|
|
931
|
+
if self._service_registry:
|
|
932
|
+
from ciris_engine.schemas.runtime.enums import ServiceType
|
|
933
|
+
|
|
934
|
+
services = self._service_registry.get_services_by_type(ServiceType.TELEMETRY)
|
|
935
|
+
telemetry_service = services[0] if services else None
|
|
936
|
+
|
|
937
|
+
if not telemetry_service:
|
|
938
|
+
logger.debug("Telemetry service not available for trace correlation")
|
|
939
|
+
return
|
|
940
|
+
|
|
941
|
+
# Create correlation
|
|
942
|
+
correlation = ServiceCorrelation(
|
|
943
|
+
correlation_id=entry.entry_id,
|
|
944
|
+
correlation_type=CorrelationType.AUDIT_EVENT,
|
|
945
|
+
service_type="audit",
|
|
946
|
+
handler_name=entry.actor,
|
|
947
|
+
action_type=action_type.value,
|
|
948
|
+
request_data=ServiceRequestData(
|
|
949
|
+
service_type="audit",
|
|
950
|
+
method_name="log_event",
|
|
951
|
+
thought_id=entry.details.get("thought_id"),
|
|
952
|
+
task_id=entry.details.get("task_id"),
|
|
953
|
+
parameters={
|
|
954
|
+
"action": action_type.value,
|
|
955
|
+
"entity_id": entry.entity_id,
|
|
956
|
+
},
|
|
957
|
+
request_timestamp=entry.timestamp,
|
|
958
|
+
),
|
|
959
|
+
response_data=ServiceResponseData(
|
|
960
|
+
success=True if entry.outcome == "success" else False,
|
|
961
|
+
execution_time_ms=0, # We don't track this for audit events
|
|
962
|
+
response_timestamp=entry.timestamp, # Use same timestamp for audit events
|
|
963
|
+
),
|
|
964
|
+
status=ServiceCorrelationStatus.COMPLETED,
|
|
965
|
+
created_at=entry.timestamp,
|
|
966
|
+
updated_at=entry.timestamp,
|
|
967
|
+
timestamp=entry.timestamp,
|
|
968
|
+
trace_context=TraceContext(
|
|
969
|
+
trace_id=f"trace_{entry.entry_id}",
|
|
970
|
+
span_id=entry.entry_id,
|
|
971
|
+
parent_span_id=entry.details.get("thought_id"),
|
|
972
|
+
span_name=f"audit.{action_type.value}",
|
|
973
|
+
),
|
|
974
|
+
tags={
|
|
975
|
+
"action": action_type.value,
|
|
976
|
+
"actor": entry.actor,
|
|
977
|
+
"severity": self._get_severity(action_type),
|
|
978
|
+
},
|
|
979
|
+
)
|
|
980
|
+
|
|
981
|
+
# Store correlation in telemetry service
|
|
982
|
+
if hasattr(telemetry_service, "_store_correlation"):
|
|
983
|
+
await telemetry_service._store_correlation(correlation)
|
|
984
|
+
logger.debug(f"Successfully stored trace correlation for audit event {entry.entry_id}")
|
|
985
|
+
else:
|
|
986
|
+
logger.warning(f"Telemetry service does not have _store_correlation method")
|
|
987
|
+
|
|
988
|
+
except Exception as e:
|
|
989
|
+
logger.error(f"Failed to create trace correlation: {e}", exc_info=True)
|
|
990
|
+
# Don't fail the audit operation if trace creation fails
|
|
991
|
+
|
|
992
|
+
async def _initialize_hash_chain(self) -> None:
|
|
993
|
+
"""Initialize hash chain components."""
|
|
994
|
+
try:
|
|
995
|
+
# Ensure directories exist
|
|
996
|
+
self.key_path.mkdir(parents=True, exist_ok=True)
|
|
997
|
+
|
|
998
|
+
# Initialize database
|
|
999
|
+
await self._init_database()
|
|
1000
|
+
|
|
1001
|
+
# Initialize components
|
|
1002
|
+
self.hash_chain = AuditHashChain(str(self.db_path))
|
|
1003
|
+
logger.debug(
|
|
1004
|
+
f"Initializing AuditSignatureManager with key_path={self.key_path}, db_path={self.db_path}, time_service={self._time_service}"
|
|
1005
|
+
)
|
|
1006
|
+
|
|
1007
|
+
# Ensure time_service is not None
|
|
1008
|
+
if not self._time_service:
|
|
1009
|
+
raise RuntimeError("TimeService is None - cannot initialize AuditSignatureManager")
|
|
1010
|
+
|
|
1011
|
+
# Check actual types
|
|
1012
|
+
logger.debug(
|
|
1013
|
+
f"Types: key_path={type(self.key_path)}, db_path={type(self.db_path)}, time_service={type(self._time_service)}"
|
|
1014
|
+
)
|
|
1015
|
+
|
|
1016
|
+
self.signature_manager = AuditSignatureManager(str(self.key_path), str(self.db_path), self._time_service)
|
|
1017
|
+
self.verifier = AuditVerifier(str(self.db_path), str(self.key_path), self._time_service)
|
|
1018
|
+
|
|
1019
|
+
# Initialize in thread
|
|
1020
|
+
await asyncio.to_thread(self._init_components_sync)
|
|
1021
|
+
|
|
1022
|
+
logger.info("Hash chain audit system initialized")
|
|
1023
|
+
|
|
1024
|
+
except Exception as e:
|
|
1025
|
+
logger.error(f"CRITICAL: Failed to initialize hash chain: {e}", exc_info=True)
|
|
1026
|
+
# Hash chain is REQUIRED for audit integrity - do not allow fallback
|
|
1027
|
+
raise RuntimeError(f"Audit hash chain initialization failed: {e}") from e
|
|
1028
|
+
|
|
1029
|
+
def _init_components_sync(self) -> None:
|
|
1030
|
+
"""Synchronous initialization of audit components."""
|
|
1031
|
+
if not self.hash_chain or not self.signature_manager or not self.verifier:
|
|
1032
|
+
raise RuntimeError("Hash chain components not initialized")
|
|
1033
|
+
|
|
1034
|
+
self.hash_chain.initialize()
|
|
1035
|
+
self.signature_manager.initialize()
|
|
1036
|
+
self.verifier.initialize()
|
|
1037
|
+
|
|
1038
|
+
if not self.signature_manager.test_signing():
|
|
1039
|
+
raise RuntimeError("Signing test failed")
|
|
1040
|
+
|
|
1041
|
+
async def _init_database(self) -> None:
|
|
1042
|
+
"""Initialize the audit database."""
|
|
1043
|
+
|
|
1044
|
+
def _create_tables() -> None:
|
|
1045
|
+
conn = sqlite3.connect(str(self.db_path), check_same_thread=False)
|
|
1046
|
+
cursor = conn.cursor()
|
|
1047
|
+
|
|
1048
|
+
# Set PRAGMA statements for stability and corruption prevention
|
|
1049
|
+
cursor.execute(PRAGMA_JOURNAL_MODE_WAL)
|
|
1050
|
+
cursor.execute(PRAGMA_BUSY_TIMEOUT_5000)
|
|
1051
|
+
cursor.execute(PRAGMA_SYNCHRONOUS_NORMAL)
|
|
1052
|
+
cursor.execute("PRAGMA foreign_keys=ON")
|
|
1053
|
+
|
|
1054
|
+
# Check database integrity
|
|
1055
|
+
integrity_result = cursor.execute("PRAGMA integrity_check").fetchone()
|
|
1056
|
+
if integrity_result[0] != "ok":
|
|
1057
|
+
logger.error(f"Audit database integrity check failed: {integrity_result}")
|
|
1058
|
+
# Close and recreate the database
|
|
1059
|
+
conn.close()
|
|
1060
|
+
import os
|
|
1061
|
+
|
|
1062
|
+
db_path_str = str(self.db_path)
|
|
1063
|
+
# Remove corrupted database and WAL/SHM files
|
|
1064
|
+
for ext in ["", "-wal", "-shm"]:
|
|
1065
|
+
try:
|
|
1066
|
+
os.remove(db_path_str + ext)
|
|
1067
|
+
except OSError:
|
|
1068
|
+
pass
|
|
1069
|
+
logger.warning("Corrupted audit database removed, recreating...")
|
|
1070
|
+
conn = sqlite3.connect(db_path_str, check_same_thread=False)
|
|
1071
|
+
cursor = conn.cursor()
|
|
1072
|
+
cursor.execute(PRAGMA_JOURNAL_MODE_WAL)
|
|
1073
|
+
cursor.execute(PRAGMA_BUSY_TIMEOUT_5000)
|
|
1074
|
+
cursor.execute(PRAGMA_SYNCHRONOUS_NORMAL)
|
|
1075
|
+
|
|
1076
|
+
# Audit log table
|
|
1077
|
+
cursor.execute(
|
|
1078
|
+
"""
|
|
1079
|
+
CREATE TABLE IF NOT EXISTS audit_log (
|
|
1080
|
+
entry_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1081
|
+
event_id TEXT NOT NULL UNIQUE,
|
|
1082
|
+
event_timestamp TEXT NOT NULL,
|
|
1083
|
+
event_type TEXT NOT NULL,
|
|
1084
|
+
originator_id TEXT NOT NULL,
|
|
1085
|
+
target_id TEXT,
|
|
1086
|
+
event_summary TEXT,
|
|
1087
|
+
event_payload TEXT,
|
|
1088
|
+
sequence_number INTEGER NOT NULL,
|
|
1089
|
+
previous_hash TEXT NOT NULL,
|
|
1090
|
+
entry_hash TEXT NOT NULL,
|
|
1091
|
+
signature TEXT NOT NULL,
|
|
1092
|
+
signing_key_id TEXT NOT NULL,
|
|
1093
|
+
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
1094
|
+
UNIQUE(sequence_number)
|
|
1095
|
+
)
|
|
1096
|
+
"""
|
|
1097
|
+
)
|
|
1098
|
+
|
|
1099
|
+
# Signing keys table
|
|
1100
|
+
cursor.execute(
|
|
1101
|
+
"""
|
|
1102
|
+
CREATE TABLE IF NOT EXISTS audit_signing_keys (
|
|
1103
|
+
key_id TEXT PRIMARY KEY,
|
|
1104
|
+
public_key TEXT NOT NULL,
|
|
1105
|
+
algorithm TEXT NOT NULL DEFAULT 'rsa-pss',
|
|
1106
|
+
key_size INTEGER NOT NULL DEFAULT 2048,
|
|
1107
|
+
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
1108
|
+
revoked_at TEXT
|
|
1109
|
+
)
|
|
1110
|
+
"""
|
|
1111
|
+
)
|
|
1112
|
+
|
|
1113
|
+
# Indexes
|
|
1114
|
+
cursor.execute(
|
|
1115
|
+
"""
|
|
1116
|
+
CREATE INDEX IF NOT EXISTS idx_audit_log_event_timestamp
|
|
1117
|
+
ON audit_log(event_timestamp)
|
|
1118
|
+
"""
|
|
1119
|
+
)
|
|
1120
|
+
|
|
1121
|
+
cursor.execute(
|
|
1122
|
+
"""
|
|
1123
|
+
CREATE INDEX IF NOT EXISTS idx_audit_log_event_type
|
|
1124
|
+
ON audit_log(event_type)
|
|
1125
|
+
"""
|
|
1126
|
+
)
|
|
1127
|
+
|
|
1128
|
+
conn.commit()
|
|
1129
|
+
conn.close()
|
|
1130
|
+
|
|
1131
|
+
await asyncio.to_thread(_create_tables)
|
|
1132
|
+
self._db_connection = sqlite3.connect(str(self.db_path), check_same_thread=False)
|
|
1133
|
+
# Apply PRAGMA settings to persistent connection
|
|
1134
|
+
self._db_connection.execute(PRAGMA_JOURNAL_MODE_WAL)
|
|
1135
|
+
self._db_connection.execute(PRAGMA_BUSY_TIMEOUT_5000)
|
|
1136
|
+
self._db_connection.execute(PRAGMA_SYNCHRONOUS_NORMAL)
|
|
1137
|
+
|
|
1138
|
+
async def _add_to_hash_chain(self, entry: AuditRequest) -> Optional[JSONDict]:
|
|
1139
|
+
"""Add an entry to the hash chain.
|
|
1140
|
+
|
|
1141
|
+
Returns:
|
|
1142
|
+
Dict with hash chain data (sequence_number, entry_hash, previous_hash, signature, signing_key_id)
|
|
1143
|
+
or None if hash chain is disabled
|
|
1144
|
+
"""
|
|
1145
|
+
if not self.enable_hash_chain:
|
|
1146
|
+
return None
|
|
1147
|
+
|
|
1148
|
+
async with self._hash_chain_lock:
|
|
1149
|
+
hash_chain_data: Optional[JSONDict] = None
|
|
1150
|
+
|
|
1151
|
+
def _write_to_chain() -> JSONDict:
|
|
1152
|
+
entry_dict: JSONDict = {
|
|
1153
|
+
"event_id": entry.entry_id,
|
|
1154
|
+
"event_timestamp": entry.timestamp.isoformat(),
|
|
1155
|
+
"event_type": entry.event_type,
|
|
1156
|
+
"originator_id": entry.entity_id,
|
|
1157
|
+
"event_payload": json.dumps(entry.details),
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
if not self.hash_chain or not self.signature_manager:
|
|
1161
|
+
raise RuntimeError("Hash chain not available")
|
|
1162
|
+
|
|
1163
|
+
prepared = self.hash_chain.prepare_entry(entry_dict)
|
|
1164
|
+
entry_hash_val = get_str(prepared, "entry_hash", "")
|
|
1165
|
+
signature = self.signature_manager.sign_entry(entry_hash_val)
|
|
1166
|
+
|
|
1167
|
+
if not self._db_connection:
|
|
1168
|
+
raise RuntimeError("Database connection not available")
|
|
1169
|
+
|
|
1170
|
+
cursor = self._db_connection.cursor()
|
|
1171
|
+
cursor.execute(
|
|
1172
|
+
"""
|
|
1173
|
+
INSERT INTO audit_log
|
|
1174
|
+
(event_id, event_timestamp, event_type, originator_id,
|
|
1175
|
+
event_summary, event_payload, sequence_number, previous_hash,
|
|
1176
|
+
entry_hash, signature, signing_key_id)
|
|
1177
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1178
|
+
""",
|
|
1179
|
+
(
|
|
1180
|
+
entry.entry_id,
|
|
1181
|
+
entry.timestamp.isoformat(),
|
|
1182
|
+
entry.event_type,
|
|
1183
|
+
entry.entity_id,
|
|
1184
|
+
f"{entry.event_type} by {entry.actor}",
|
|
1185
|
+
json.dumps(entry.details),
|
|
1186
|
+
prepared["sequence_number"],
|
|
1187
|
+
prepared["previous_hash"],
|
|
1188
|
+
prepared["entry_hash"],
|
|
1189
|
+
signature,
|
|
1190
|
+
self.signature_manager.key_id or "unknown",
|
|
1191
|
+
),
|
|
1192
|
+
)
|
|
1193
|
+
|
|
1194
|
+
self._db_connection.commit()
|
|
1195
|
+
|
|
1196
|
+
# Return hash chain data
|
|
1197
|
+
result_dict: JSONDict = {
|
|
1198
|
+
"sequence_number": get_int(prepared, "sequence_number", 0),
|
|
1199
|
+
"entry_hash": entry_hash_val,
|
|
1200
|
+
"previous_hash": get_str(prepared, "previous_hash", ""),
|
|
1201
|
+
"signature": signature,
|
|
1202
|
+
"signing_key_id": self.signature_manager.key_id or "unknown",
|
|
1203
|
+
}
|
|
1204
|
+
return result_dict
|
|
1205
|
+
|
|
1206
|
+
try:
|
|
1207
|
+
logger.debug(f"About to write to hash chain for entry {entry.entry_id}")
|
|
1208
|
+
hash_chain_data = await asyncio.to_thread(_write_to_chain)
|
|
1209
|
+
logger.debug(f"Successfully wrote to hash chain for entry {entry.entry_id}")
|
|
1210
|
+
return hash_chain_data
|
|
1211
|
+
except Exception as e:
|
|
1212
|
+
logger.error(f"Failed to add to hash chain: {e}", exc_info=True)
|
|
1213
|
+
return None
|
|
1214
|
+
|
|
1215
|
+
def _cache_entry(self, entry: AuditRequest) -> None:
|
|
1216
|
+
"""Add entry to cache."""
|
|
1217
|
+
self._recent_entries.append(entry)
|
|
1218
|
+
if len(self._recent_entries) > self._max_cached_entries:
|
|
1219
|
+
self._recent_entries = self._recent_entries[-self._max_cached_entries :]
|
|
1220
|
+
|
|
1221
|
+
async def _export_worker(self) -> None:
|
|
1222
|
+
"""Background task to export audit data."""
|
|
1223
|
+
while True:
|
|
1224
|
+
try:
|
|
1225
|
+
await asyncio.sleep(60) # Export every minute
|
|
1226
|
+
if self._export_buffer:
|
|
1227
|
+
await self._flush_exports()
|
|
1228
|
+
except asyncio.CancelledError:
|
|
1229
|
+
logger.debug("Export worker cancelled")
|
|
1230
|
+
raise # Re-raise to properly exit the task
|
|
1231
|
+
except Exception as e:
|
|
1232
|
+
logger.error(f"Export worker error: {e}")
|
|
1233
|
+
|
|
1234
|
+
async def _flush_exports(self) -> None:
|
|
1235
|
+
"""Flush export buffer to file."""
|
|
1236
|
+
if not self._export_buffer or not self.export_path:
|
|
1237
|
+
return
|
|
1238
|
+
|
|
1239
|
+
try:
|
|
1240
|
+
if self.export_format == "jsonl":
|
|
1241
|
+
await self._export_jsonl(self._export_buffer, self.export_path)
|
|
1242
|
+
elif self.export_format == "csv":
|
|
1243
|
+
await self._export_csv(self._export_buffer, self.export_path)
|
|
1244
|
+
elif self.export_format == "sqlite":
|
|
1245
|
+
await self._export_sqlite(self._export_buffer, self.export_path)
|
|
1246
|
+
|
|
1247
|
+
self._export_buffer.clear()
|
|
1248
|
+
except Exception as e:
|
|
1249
|
+
logger.error(f"Failed to flush exports: {e}")
|
|
1250
|
+
|
|
1251
|
+
async def _export_jsonl(self, entries: List[AuditRequest], path: Path) -> None:
|
|
1252
|
+
"""Export entries to JSONL format."""
|
|
1253
|
+
|
|
1254
|
+
def _write_jsonl() -> None:
|
|
1255
|
+
with open(path, "a") as f:
|
|
1256
|
+
for entry in entries:
|
|
1257
|
+
f.write(json.dumps(entry.model_dump(), default=str) + "\n")
|
|
1258
|
+
|
|
1259
|
+
await asyncio.to_thread(_write_jsonl)
|
|
1260
|
+
|
|
1261
|
+
async def _export_csv(self, entries: List[AuditRequest], path: Path) -> None:
|
|
1262
|
+
"""Export entries to CSV format."""
|
|
1263
|
+
import csv
|
|
1264
|
+
|
|
1265
|
+
def _write_csv() -> None:
|
|
1266
|
+
file_exists = path.exists()
|
|
1267
|
+
with open(path, "a", newline="") as f:
|
|
1268
|
+
writer = csv.writer(f)
|
|
1269
|
+
|
|
1270
|
+
# Write header if new file
|
|
1271
|
+
if not file_exists:
|
|
1272
|
+
writer.writerow(["entry_id", "timestamp", "entity_id", "event_type", "actor", "outcome", "details"])
|
|
1273
|
+
|
|
1274
|
+
# Write entries
|
|
1275
|
+
for entry in entries:
|
|
1276
|
+
writer.writerow(
|
|
1277
|
+
[
|
|
1278
|
+
entry.entry_id,
|
|
1279
|
+
entry.timestamp.isoformat(),
|
|
1280
|
+
entry.entity_id,
|
|
1281
|
+
entry.event_type,
|
|
1282
|
+
entry.actor,
|
|
1283
|
+
entry.outcome,
|
|
1284
|
+
json.dumps(entry.details),
|
|
1285
|
+
]
|
|
1286
|
+
)
|
|
1287
|
+
|
|
1288
|
+
await asyncio.to_thread(_write_csv)
|
|
1289
|
+
|
|
1290
|
+
async def _export_sqlite(self, entries: List[AuditRequest], path: Path) -> None:
|
|
1291
|
+
"""Export entries to SQLite format."""
|
|
1292
|
+
|
|
1293
|
+
def _write_sqlite() -> None:
|
|
1294
|
+
conn = sqlite3.connect(str(path), check_same_thread=False)
|
|
1295
|
+
cursor = conn.cursor()
|
|
1296
|
+
|
|
1297
|
+
# Create table if needed
|
|
1298
|
+
cursor.execute(
|
|
1299
|
+
"""
|
|
1300
|
+
CREATE TABLE IF NOT EXISTS audit_export (
|
|
1301
|
+
entry_id TEXT PRIMARY KEY,
|
|
1302
|
+
timestamp TEXT NOT NULL,
|
|
1303
|
+
entity_id TEXT NOT NULL,
|
|
1304
|
+
event_type TEXT NOT NULL,
|
|
1305
|
+
actor TEXT NOT NULL,
|
|
1306
|
+
outcome TEXT,
|
|
1307
|
+
details TEXT
|
|
1308
|
+
)
|
|
1309
|
+
"""
|
|
1310
|
+
)
|
|
1311
|
+
|
|
1312
|
+
# Use dialect-aware query builder for UPSERT
|
|
1313
|
+
adapter = get_adapter()
|
|
1314
|
+
builder = adapter.get_query_builder()
|
|
1315
|
+
|
|
1316
|
+
query = builder.insert(
|
|
1317
|
+
table="audit_export",
|
|
1318
|
+
columns=["entry_id", "timestamp", "entity_id", "event_type", "actor", "outcome", "details"],
|
|
1319
|
+
conflict_resolution=ConflictResolution.REPLACE,
|
|
1320
|
+
conflict_columns=["entry_id"],
|
|
1321
|
+
)
|
|
1322
|
+
sql = query.to_sql(adapter)
|
|
1323
|
+
|
|
1324
|
+
# Insert entries
|
|
1325
|
+
for entry in entries:
|
|
1326
|
+
cursor.execute(
|
|
1327
|
+
sql,
|
|
1328
|
+
(
|
|
1329
|
+
entry.entry_id,
|
|
1330
|
+
entry.timestamp.isoformat(),
|
|
1331
|
+
entry.entity_id,
|
|
1332
|
+
entry.event_type,
|
|
1333
|
+
entry.actor,
|
|
1334
|
+
entry.outcome,
|
|
1335
|
+
json.dumps(entry.details),
|
|
1336
|
+
),
|
|
1337
|
+
)
|
|
1338
|
+
|
|
1339
|
+
conn.commit()
|
|
1340
|
+
conn.close()
|
|
1341
|
+
|
|
1342
|
+
await asyncio.to_thread(_write_sqlite)
|
|
1343
|
+
|
|
1344
|
+
def _get_severity(self, action: HandlerActionType) -> str:
|
|
1345
|
+
"""Determine severity level for an action."""
|
|
1346
|
+
if action in [HandlerActionType.DEFER, HandlerActionType.REJECT, HandlerActionType.FORGET]:
|
|
1347
|
+
return "high"
|
|
1348
|
+
elif action in [HandlerActionType.TOOL, HandlerActionType.MEMORIZE, HandlerActionType.TASK_COMPLETE]:
|
|
1349
|
+
return "medium"
|
|
1350
|
+
else:
|
|
1351
|
+
return "low"
|
|
1352
|
+
|
|
1353
|
+
def _calculate_hours(self, start_time: Optional[datetime], end_time: Optional[datetime]) -> int:
|
|
1354
|
+
"""Calculate hours for time range."""
|
|
1355
|
+
if start_time and end_time:
|
|
1356
|
+
return int((end_time - start_time).total_seconds() / 3600)
|
|
1357
|
+
elif start_time:
|
|
1358
|
+
return int(
|
|
1359
|
+
((self._time_service.now() if self._time_service else datetime.now()) - start_time).total_seconds()
|
|
1360
|
+
/ 3600
|
|
1361
|
+
)
|
|
1362
|
+
else:
|
|
1363
|
+
return 24 * 30 # Default 30 days
|
|
1364
|
+
|
|
1365
|
+
def _matches_filters(
|
|
1366
|
+
self,
|
|
1367
|
+
data: GraphNode,
|
|
1368
|
+
start_time: Optional[datetime],
|
|
1369
|
+
end_time: Optional[datetime],
|
|
1370
|
+
action_types: Optional[List[str]],
|
|
1371
|
+
thought_id: Optional[str],
|
|
1372
|
+
task_id: Optional[str],
|
|
1373
|
+
) -> bool:
|
|
1374
|
+
"""Check if data matches query filters."""
|
|
1375
|
+
# Time filters
|
|
1376
|
+
timestamp = data.attributes.created_at if hasattr(data.attributes, "created_at") else data.updated_at
|
|
1377
|
+
if timestamp:
|
|
1378
|
+
if start_time and timestamp < start_time:
|
|
1379
|
+
return False
|
|
1380
|
+
if end_time and timestamp > end_time:
|
|
1381
|
+
return False
|
|
1382
|
+
|
|
1383
|
+
# Action type filter
|
|
1384
|
+
tags = data.attributes.tags if hasattr(data.attributes, "tags") else []
|
|
1385
|
+
_tag_dict = dict.fromkeys(tags, True) # Convert list to dict for lookup
|
|
1386
|
+
|
|
1387
|
+
# Check attributes dict as well
|
|
1388
|
+
attrs = data.attributes.model_dump() if hasattr(data.attributes, "model_dump") else {}
|
|
1389
|
+
|
|
1390
|
+
if action_types and attrs.get("action_type") not in action_types:
|
|
1391
|
+
return False
|
|
1392
|
+
|
|
1393
|
+
# Entity filters
|
|
1394
|
+
if thought_id and attrs.get("thought_id") != thought_id:
|
|
1395
|
+
return False
|
|
1396
|
+
if task_id and attrs.get("task_id") != task_id:
|
|
1397
|
+
return False
|
|
1398
|
+
|
|
1399
|
+
return True
|
|
1400
|
+
|
|
1401
|
+
def _extract_thought_id_from_audit_node(self, audit_node: AuditEntryNode) -> str:
|
|
1402
|
+
"""Extract thought_id from audit node context."""
|
|
1403
|
+
if not audit_node.context.additional_data:
|
|
1404
|
+
return ""
|
|
1405
|
+
value = audit_node.context.additional_data.get("thought_id", "")
|
|
1406
|
+
return str(value) if value else ""
|
|
1407
|
+
|
|
1408
|
+
def _extract_task_id_from_audit_node(self, audit_node: AuditEntryNode) -> str:
|
|
1409
|
+
"""Extract task_id from audit node context."""
|
|
1410
|
+
if not audit_node.context.additional_data:
|
|
1411
|
+
return ""
|
|
1412
|
+
value = audit_node.context.additional_data.get("task_id", "")
|
|
1413
|
+
return str(value) if value else ""
|
|
1414
|
+
|
|
1415
|
+
def _extract_outcome_from_audit_node(self, audit_node: AuditEntryNode) -> Optional[str]:
|
|
1416
|
+
"""Extract outcome from audit node context."""
|
|
1417
|
+
if not audit_node.context.additional_data:
|
|
1418
|
+
return None
|
|
1419
|
+
value = audit_node.context.additional_data.get("outcome")
|
|
1420
|
+
return str(value) if value is not None else None
|
|
1421
|
+
|
|
1422
|
+
def _convert_audit_entry_node(self, audit_node: AuditEntryNode) -> AuditRequest:
|
|
1423
|
+
"""Convert AuditEntryNode to AuditRequest."""
|
|
1424
|
+
return AuditRequest(
|
|
1425
|
+
entry_id=audit_node.id.replace("audit_", ""),
|
|
1426
|
+
timestamp=audit_node.timestamp,
|
|
1427
|
+
entity_id=audit_node.context.correlation_id or "",
|
|
1428
|
+
event_type=audit_node.action,
|
|
1429
|
+
actor=audit_node.actor,
|
|
1430
|
+
details={
|
|
1431
|
+
"action_type": audit_node.action,
|
|
1432
|
+
"thought_id": self._extract_thought_id_from_audit_node(audit_node),
|
|
1433
|
+
"task_id": self._extract_task_id_from_audit_node(audit_node),
|
|
1434
|
+
"handler_name": audit_node.context.service_name or "",
|
|
1435
|
+
"context": audit_node.context.model_dump(),
|
|
1436
|
+
},
|
|
1437
|
+
outcome=self._extract_outcome_from_audit_node(audit_node),
|
|
1438
|
+
)
|
|
1439
|
+
|
|
1440
|
+
def _get_timestamp_from_data(self, data: GraphNode) -> datetime:
|
|
1441
|
+
"""Get timestamp from data node with fallback."""
|
|
1442
|
+
timestamp = data.attributes.created_at if hasattr(data.attributes, "created_at") else data.updated_at
|
|
1443
|
+
if not timestamp:
|
|
1444
|
+
timestamp = self._time_service.now() if self._time_service else datetime.now()
|
|
1445
|
+
return timestamp
|
|
1446
|
+
|
|
1447
|
+
def _extract_action_type_from_attrs(self, attrs: JSONDict) -> Optional[str]:
|
|
1448
|
+
"""Extract action_type from attributes with fallback."""
|
|
1449
|
+
action_type_val = get_str(attrs, "action_type", "")
|
|
1450
|
+
if action_type_val:
|
|
1451
|
+
return action_type_val
|
|
1452
|
+
return None
|
|
1453
|
+
|
|
1454
|
+
def _create_audit_request_from_attrs(self, attrs: JSONDict, timestamp: datetime, action_type: str) -> AuditRequest:
|
|
1455
|
+
"""Create AuditRequest from manual attribute parsing."""
|
|
1456
|
+
return AuditRequest(
|
|
1457
|
+
entry_id=attrs.get("event_id", str(uuid4())),
|
|
1458
|
+
timestamp=timestamp,
|
|
1459
|
+
entity_id=attrs.get("thought_id", "") or attrs.get("task_id", ""),
|
|
1460
|
+
event_type=action_type,
|
|
1461
|
+
actor=attrs.get("actor", attrs.get("handler_name", "system")),
|
|
1462
|
+
details={
|
|
1463
|
+
"action_type": action_type,
|
|
1464
|
+
"thought_id": attrs.get("thought_id", ""),
|
|
1465
|
+
"task_id": attrs.get("task_id", ""),
|
|
1466
|
+
"handler_name": attrs.get("handler_name", ""),
|
|
1467
|
+
"attributes": attrs,
|
|
1468
|
+
},
|
|
1469
|
+
outcome=attrs.get("outcome"),
|
|
1470
|
+
)
|
|
1471
|
+
|
|
1472
|
+
def _tsdb_to_audit_entry(self, data: GraphNode) -> Optional[AuditRequest]:
|
|
1473
|
+
"""Convert TSDB node to AuditEntry."""
|
|
1474
|
+
# Check if this is an AuditEntryNode by looking for the marker
|
|
1475
|
+
attrs = data.attributes if isinstance(data.attributes, dict) else {}
|
|
1476
|
+
|
|
1477
|
+
# If it's an AuditEntryNode stored with to_graph_node(), convert back
|
|
1478
|
+
if attrs.get("node_class") == "AuditEntry":
|
|
1479
|
+
try:
|
|
1480
|
+
audit_node = AuditEntryNode.from_graph_node(data)
|
|
1481
|
+
return self._convert_audit_entry_node(audit_node)
|
|
1482
|
+
except Exception as e:
|
|
1483
|
+
logger.warning(f"Failed to convert AuditEntryNode: {e}, falling back to manual parsing")
|
|
1484
|
+
|
|
1485
|
+
# Fallback: manual parsing for backwards compatibility
|
|
1486
|
+
attrs = data.attributes.model_dump() if hasattr(data.attributes, "model_dump") else {}
|
|
1487
|
+
|
|
1488
|
+
action_type = self._extract_action_type_from_attrs(attrs)
|
|
1489
|
+
if not action_type:
|
|
1490
|
+
return None
|
|
1491
|
+
|
|
1492
|
+
timestamp = self._get_timestamp_from_data(data)
|
|
1493
|
+
return self._create_audit_request_from_attrs(attrs, timestamp, action_type)
|
|
1494
|
+
|
|
1495
|
+
def _convert_timeseries_to_entries(
|
|
1496
|
+
self, timeseries_data: List[TimeSeriesDataPoint], entity_id: Optional[str] = None
|
|
1497
|
+
) -> List[AuditEntry]:
|
|
1498
|
+
"""Convert timeseries data to audit entries."""
|
|
1499
|
+
results: List[AuditEntry] = []
|
|
1500
|
+
|
|
1501
|
+
for data in timeseries_data:
|
|
1502
|
+
# Filter by entity if specified
|
|
1503
|
+
if entity_id:
|
|
1504
|
+
tags = data.tags or {}
|
|
1505
|
+
if entity_id not in [tags.get("thought_id"), tags.get("task_id")]:
|
|
1506
|
+
continue
|
|
1507
|
+
|
|
1508
|
+
# Convert TimeSeriesDataPoint to GraphNode for compatibility
|
|
1509
|
+
# TimeSeriesDataPoint doesn't directly map to audit entries, skip
|
|
1510
|
+
# This method seems to be looking for audit entries stored as timeseries
|
|
1511
|
+
# but TimeSeriesDataPoint is for metrics, not audit entries
|
|
1512
|
+
continue
|
|
1513
|
+
|
|
1514
|
+
return results
|
|
1515
|
+
|
|
1516
|
+
async def query_events(
|
|
1517
|
+
self,
|
|
1518
|
+
event_type: Optional[str] = None,
|
|
1519
|
+
start_time: Optional[datetime] = None,
|
|
1520
|
+
end_time: Optional[datetime] = None,
|
|
1521
|
+
limit: int = 100,
|
|
1522
|
+
) -> List["AuditLogEntry"]:
|
|
1523
|
+
"""Query audit events."""
|
|
1524
|
+
# Call query_audit_trail directly with parameters
|
|
1525
|
+
from ciris_engine.schemas.services.graph.audit import AuditQuery
|
|
1526
|
+
|
|
1527
|
+
query = AuditQuery(
|
|
1528
|
+
start_time=start_time, end_time=end_time, action_types=[event_type] if event_type else None, limit=limit
|
|
1529
|
+
)
|
|
1530
|
+
entries = await self.query_audit_trail(query)
|
|
1531
|
+
|
|
1532
|
+
# Convert to AuditLogEntry format
|
|
1533
|
+
from ciris_engine.schemas.audit.core import AuditLogEntry, EventPayload
|
|
1534
|
+
|
|
1535
|
+
result = []
|
|
1536
|
+
for entry in entries:
|
|
1537
|
+
# Create EventPayload from entry context
|
|
1538
|
+
event_payload = EventPayload(
|
|
1539
|
+
action=entry.action, user_id=entry.actor, service_name=getattr(entry, "resource", "audit_service")
|
|
1540
|
+
)
|
|
1541
|
+
|
|
1542
|
+
# Create AuditLogEntry
|
|
1543
|
+
entity_id = entry.context.correlation_id or ""
|
|
1544
|
+
log_entry = AuditLogEntry(
|
|
1545
|
+
event_id=entry.id,
|
|
1546
|
+
event_timestamp=entry.timestamp,
|
|
1547
|
+
event_type=entry.action,
|
|
1548
|
+
originator_id=entry.actor,
|
|
1549
|
+
target_id=entity_id,
|
|
1550
|
+
event_summary=f"{entry.action} by {entry.actor}",
|
|
1551
|
+
event_payload=event_payload,
|
|
1552
|
+
thought_id=entity_id if entity_id.startswith("thought") else None,
|
|
1553
|
+
entry_hash=entry.signature,
|
|
1554
|
+
)
|
|
1555
|
+
result.append(log_entry)
|
|
1556
|
+
return result
|
|
1557
|
+
|
|
1558
|
+
def _convert_entry_to_audit_log_dict(self, entry: AuditRequest) -> JSONDict:
|
|
1559
|
+
"""Convert audit entry to audit log dictionary format."""
|
|
1560
|
+
return {
|
|
1561
|
+
"event_id": entry.entry_id,
|
|
1562
|
+
"event_type": entry.event_type,
|
|
1563
|
+
"timestamp": entry.timestamp.isoformat() if entry.timestamp else None,
|
|
1564
|
+
"user_id": entry.actor,
|
|
1565
|
+
"data": entry.details,
|
|
1566
|
+
"metadata": {"outcome": entry.outcome} if entry.outcome else {},
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
async def _search_event_in_memory_bus(self, event_id: str) -> Optional[JSONDict]:
|
|
1570
|
+
"""Search for event in memory bus."""
|
|
1571
|
+
if not self._memory_bus:
|
|
1572
|
+
return None
|
|
1573
|
+
|
|
1574
|
+
query = MemoryQuery(
|
|
1575
|
+
node_id=event_id, scope=GraphScope.LOCAL, type=NodeType.AUDIT_ENTRY, include_edges=False, depth=1
|
|
1576
|
+
)
|
|
1577
|
+
|
|
1578
|
+
nodes = await self._memory_bus.recall(query)
|
|
1579
|
+
if not nodes or len(nodes) == 0:
|
|
1580
|
+
return None
|
|
1581
|
+
|
|
1582
|
+
entry = self._tsdb_to_audit_entry(nodes[0])
|
|
1583
|
+
if not entry:
|
|
1584
|
+
return None
|
|
1585
|
+
|
|
1586
|
+
return self._convert_entry_to_audit_log_dict(entry)
|
|
1587
|
+
|
|
1588
|
+
def _search_event_in_recent_cache(self, event_id: str) -> Optional[JSONDict]:
|
|
1589
|
+
"""Search for event in recent entries cache."""
|
|
1590
|
+
for entry in self._recent_entries:
|
|
1591
|
+
if entry.entry_id == event_id:
|
|
1592
|
+
return self._convert_entry_to_audit_log_dict(entry)
|
|
1593
|
+
return None
|
|
1594
|
+
|
|
1595
|
+
async def get_event_by_id(self, event_id: str) -> Optional["AuditLogEntry"]:
|
|
1596
|
+
"""Get specific audit event by ID."""
|
|
1597
|
+
from ciris_engine.schemas.audit.core import AuditLogEntry, EventPayload
|
|
1598
|
+
|
|
1599
|
+
# Try memory bus first
|
|
1600
|
+
result_dict = await self._search_event_in_memory_bus(event_id)
|
|
1601
|
+
if not result_dict:
|
|
1602
|
+
# Fall back to recent cache
|
|
1603
|
+
result_dict = self._search_event_in_recent_cache(event_id)
|
|
1604
|
+
|
|
1605
|
+
if not result_dict:
|
|
1606
|
+
return None
|
|
1607
|
+
|
|
1608
|
+
# Convert dict to AuditLogEntry
|
|
1609
|
+
return AuditLogEntry(
|
|
1610
|
+
event_id=result_dict.get("event_id", event_id),
|
|
1611
|
+
event_timestamp=result_dict.get("timestamp"),
|
|
1612
|
+
event_type=result_dict.get("event_type", ""),
|
|
1613
|
+
originator_id=result_dict.get("user_id", ""),
|
|
1614
|
+
target_id=result_dict.get("user_id", ""),
|
|
1615
|
+
event_summary=f"{result_dict.get('event_type', '')} event",
|
|
1616
|
+
event_payload=EventPayload(
|
|
1617
|
+
action=result_dict.get("event_type", ""),
|
|
1618
|
+
service_name="audit_service",
|
|
1619
|
+
user_id=result_dict.get("user_id", ""),
|
|
1620
|
+
),
|
|
1621
|
+
)
|
|
1622
|
+
|
|
1623
|
+
def _audit_request_to_entry(self, req: AuditRequest) -> AuditEntry:
|
|
1624
|
+
"""Convert AuditRequest to AuditEntry."""
|
|
1625
|
+
return AuditEntryNode(
|
|
1626
|
+
id=f"audit_{req.entry_id}",
|
|
1627
|
+
action=req.event_type,
|
|
1628
|
+
actor=req.actor,
|
|
1629
|
+
timestamp=req.timestamp,
|
|
1630
|
+
context=AuditEntryContext(
|
|
1631
|
+
service_name=req.details.get("handler_name", ""),
|
|
1632
|
+
correlation_id=req.entity_id,
|
|
1633
|
+
additional_data={k: str(v) for k, v in req.details.items()},
|
|
1634
|
+
),
|
|
1635
|
+
signature=None,
|
|
1636
|
+
hash_chain=None,
|
|
1637
|
+
scope=GraphScope.LOCAL,
|
|
1638
|
+
attributes={},
|
|
1639
|
+
)
|
|
1640
|
+
|
|
1641
|
+
# Required methods for BaseGraphService
|
|
1642
|
+
|
|
1643
|
+
def get_service_type(self) -> ServiceType:
|
|
1644
|
+
"""Get the service type."""
|
|
1645
|
+
return ServiceType.AUDIT
|
|
1646
|
+
|
|
1647
|
+
def _get_actions(self) -> List[str]:
|
|
1648
|
+
"""Get the list of actions this service supports."""
|
|
1649
|
+
return [
|
|
1650
|
+
"log_action",
|
|
1651
|
+
"log_event",
|
|
1652
|
+
"log_request",
|
|
1653
|
+
"get_audit_trail",
|
|
1654
|
+
"query_audit_trail",
|
|
1655
|
+
"query_by_actor",
|
|
1656
|
+
"query_by_time_range",
|
|
1657
|
+
"export_audit_log",
|
|
1658
|
+
"verify_integrity",
|
|
1659
|
+
"verify_signatures",
|
|
1660
|
+
"get_complete_verification_report",
|
|
1661
|
+
"query_events",
|
|
1662
|
+
"get_event_by_id",
|
|
1663
|
+
"verify_audit_integrity",
|
|
1664
|
+
]
|
|
1665
|
+
|
|
1666
|
+
def _check_dependencies(self) -> bool:
|
|
1667
|
+
"""Check if all dependencies are satisfied."""
|
|
1668
|
+
# Check parent dependencies (memory bus)
|
|
1669
|
+
if not super()._check_dependencies():
|
|
1670
|
+
return False
|
|
1671
|
+
|
|
1672
|
+
# No need to check hash_chain here - it's initialized during start()
|
|
1673
|
+
# The hash_chain is an internal component, not an external dependency
|
|
1674
|
+
|
|
1675
|
+
return True
|