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,1692 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TSDB Consolidation Service - Main service class.
|
|
3
|
+
|
|
4
|
+
This service runs every 6 hours to consolidate telemetry and memory data
|
|
5
|
+
into permanent summary records with proper edge connections.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import logging
|
|
10
|
+
import random
|
|
11
|
+
from datetime import datetime, timedelta, timezone
|
|
12
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union
|
|
13
|
+
from uuid import uuid4
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from ciris_engine.logic.registries.base import ServiceRegistry
|
|
17
|
+
|
|
18
|
+
from ciris_engine.constants import UTC_TIMEZONE_SUFFIX
|
|
19
|
+
from ciris_engine.logic.buses.memory_bus import MemoryBus
|
|
20
|
+
from ciris_engine.logic.services.base_graph_service import BaseGraphService
|
|
21
|
+
from ciris_engine.protocols.infrastructure.base import RegistryAwareServiceProtocol, ServiceRegistryProtocol
|
|
22
|
+
from ciris_engine.protocols.services.lifecycle.time import TimeServiceProtocol
|
|
23
|
+
from ciris_engine.schemas.consent.core import ConsentStream
|
|
24
|
+
from ciris_engine.schemas.runtime.enums import ServiceType
|
|
25
|
+
from ciris_engine.schemas.services.core import ServiceCapabilities, ServiceStatus
|
|
26
|
+
from ciris_engine.schemas.services.graph.consolidation import (
|
|
27
|
+
MetricCorrelationData,
|
|
28
|
+
ServiceInteractionData,
|
|
29
|
+
TaskCorrelationData,
|
|
30
|
+
TraceSpanData,
|
|
31
|
+
TSDBPeriodSummary,
|
|
32
|
+
)
|
|
33
|
+
from ciris_engine.schemas.services.graph.query_results import ServiceCorrelationQueryResult, TSDBNodeQueryResult
|
|
34
|
+
from ciris_engine.schemas.services.graph.tsdb_models import SummaryAttributes
|
|
35
|
+
from ciris_engine.schemas.services.graph_core import GraphNode, NodeType
|
|
36
|
+
from ciris_engine.schemas.services.operations import MemoryOpStatus
|
|
37
|
+
|
|
38
|
+
from .consolidators import (
|
|
39
|
+
AuditConsolidator,
|
|
40
|
+
ConversationConsolidator,
|
|
41
|
+
MemoryConsolidator,
|
|
42
|
+
MetricsConsolidator,
|
|
43
|
+
TaskConsolidator,
|
|
44
|
+
TraceConsolidator,
|
|
45
|
+
)
|
|
46
|
+
from .edge_manager import EdgeManager
|
|
47
|
+
from .period_manager import PeriodManager
|
|
48
|
+
from .query_manager import QueryManager
|
|
49
|
+
|
|
50
|
+
logger = logging.getLogger(__name__)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class TSDBConsolidationService(BaseGraphService, RegistryAwareServiceProtocol):
|
|
54
|
+
"""
|
|
55
|
+
Refactored TSDB Consolidation Service.
|
|
56
|
+
|
|
57
|
+
Key improvements:
|
|
58
|
+
1. Consolidates BOTH graph nodes AND service correlations
|
|
59
|
+
2. Creates proper edges in graph_edges table
|
|
60
|
+
3. Links summaries to ALL nodes in the period
|
|
61
|
+
4. Includes task summaries with outcomes
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
def __init__(
|
|
65
|
+
self,
|
|
66
|
+
memory_bus: Optional[MemoryBus] = None,
|
|
67
|
+
time_service: Optional[TimeServiceProtocol] = None,
|
|
68
|
+
consolidation_interval_hours: int = 6,
|
|
69
|
+
raw_retention_hours: int = 24,
|
|
70
|
+
db_path: Optional[str] = None,
|
|
71
|
+
) -> None:
|
|
72
|
+
"""
|
|
73
|
+
Initialize the consolidation service.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
memory_bus: Bus for memory operations
|
|
77
|
+
time_service: Time service for consistent timestamps
|
|
78
|
+
consolidation_interval_hours: How often to run (default: 6)
|
|
79
|
+
raw_retention_hours: How long to keep raw data (default: 24)
|
|
80
|
+
db_path: Database path to use (if not provided, uses default)
|
|
81
|
+
"""
|
|
82
|
+
super().__init__(memory_bus=memory_bus, time_service=time_service)
|
|
83
|
+
self.service_name = "TSDBConsolidationService"
|
|
84
|
+
self.db_path = db_path
|
|
85
|
+
|
|
86
|
+
# Initialize components
|
|
87
|
+
self._period_manager = PeriodManager(consolidation_interval_hours)
|
|
88
|
+
self._query_manager = QueryManager(memory_bus, db_path=db_path)
|
|
89
|
+
self._edge_manager = EdgeManager(db_path=db_path)
|
|
90
|
+
|
|
91
|
+
# Initialize all consolidators
|
|
92
|
+
self._metrics_consolidator = MetricsConsolidator(memory_bus)
|
|
93
|
+
self._memory_consolidator = MemoryConsolidator(memory_bus)
|
|
94
|
+
self._task_consolidator = TaskConsolidator(memory_bus)
|
|
95
|
+
self._conversation_consolidator = ConversationConsolidator(memory_bus, time_service)
|
|
96
|
+
self._trace_consolidator = TraceConsolidator(memory_bus)
|
|
97
|
+
self._audit_consolidator = AuditConsolidator(memory_bus, time_service)
|
|
98
|
+
|
|
99
|
+
self._consolidation_interval = timedelta(hours=consolidation_interval_hours)
|
|
100
|
+
self._raw_retention = timedelta(hours=raw_retention_hours)
|
|
101
|
+
|
|
102
|
+
# Load consolidation intervals from config
|
|
103
|
+
self._load_consolidation_config()
|
|
104
|
+
|
|
105
|
+
# Retention periods for different levels
|
|
106
|
+
self._basic_retention = timedelta(days=7) # Keep basic summaries for 7 days
|
|
107
|
+
self._extensive_retention = timedelta(days=30) # Keep daily summaries for 30 days
|
|
108
|
+
|
|
109
|
+
# Task management
|
|
110
|
+
self._consolidation_task: Optional[asyncio.Task[None]] = None
|
|
111
|
+
self._running = False
|
|
112
|
+
|
|
113
|
+
# Track last successful consolidation
|
|
114
|
+
self._last_consolidation: Optional[datetime] = None
|
|
115
|
+
self._last_extensive_consolidation: Optional[datetime] = None
|
|
116
|
+
self._last_profound_consolidation: Optional[datetime] = None
|
|
117
|
+
self._start_time: Optional[datetime] = None
|
|
118
|
+
|
|
119
|
+
# Telemetry tracking variables
|
|
120
|
+
self._basic_consolidations = 0
|
|
121
|
+
self._extensive_consolidations = 0
|
|
122
|
+
self._profound_consolidations = 0
|
|
123
|
+
self._records_consolidated = 0
|
|
124
|
+
self._records_deleted = 0
|
|
125
|
+
self._compression_ratio = 1.0
|
|
126
|
+
self._last_consolidation_duration = 0.0
|
|
127
|
+
self._consolidation_errors = 0
|
|
128
|
+
self._start_time = None # Will be set when service starts
|
|
129
|
+
|
|
130
|
+
def _load_consolidation_config(self) -> None:
|
|
131
|
+
"""Load consolidation configuration from essential config."""
|
|
132
|
+
# Fixed intervals for calendar alignment
|
|
133
|
+
self._basic_interval = timedelta(hours=6) # 00:00, 06:00, 12:00, 18:00 UTC
|
|
134
|
+
self._extensive_interval = timedelta(days=7) # Weekly on Mondays
|
|
135
|
+
self._profound_interval = timedelta(days=30) # Monthly on 1st
|
|
136
|
+
|
|
137
|
+
# Load configurable values
|
|
138
|
+
# Set default configurable values
|
|
139
|
+
self._profound_target_mb_per_day = 20.0 # Default 20MB/day
|
|
140
|
+
logger.info(f"TSDB profound consolidation target: {self._profound_target_mb_per_day} MB/day")
|
|
141
|
+
|
|
142
|
+
async def attach_registry(self, registry: "ServiceRegistryProtocol") -> None:
|
|
143
|
+
"""
|
|
144
|
+
Attach service registry for service discovery.
|
|
145
|
+
|
|
146
|
+
Implements RegistryAwareServiceProtocol to enable proper initialization
|
|
147
|
+
of time service dependency.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
registry: Service registry providing access to services
|
|
151
|
+
"""
|
|
152
|
+
self._service_registry = registry
|
|
153
|
+
|
|
154
|
+
# Only get time service from registry if not provided
|
|
155
|
+
if not self._time_service and registry:
|
|
156
|
+
from ciris_engine.schemas.runtime.enums import ServiceType
|
|
157
|
+
|
|
158
|
+
time_services = registry.get_services_by_type(ServiceType.TIME)
|
|
159
|
+
if time_services:
|
|
160
|
+
self._time_service = time_services[0]
|
|
161
|
+
|
|
162
|
+
def _now(self) -> datetime:
|
|
163
|
+
"""Get current time from time service."""
|
|
164
|
+
return self._time_service.now() if self._time_service else datetime.now(timezone.utc)
|
|
165
|
+
|
|
166
|
+
async def start(self) -> None:
|
|
167
|
+
"""Start the consolidation service."""
|
|
168
|
+
if self._running:
|
|
169
|
+
logger.warning("TSDBConsolidationService already running")
|
|
170
|
+
return
|
|
171
|
+
|
|
172
|
+
await super().start()
|
|
173
|
+
self._running = True
|
|
174
|
+
self._start_time = self._now()
|
|
175
|
+
|
|
176
|
+
# Start single consolidation loop that handles basic → extensive → profound sequentially
|
|
177
|
+
# The loop will consolidate missed windows first before entering the regular schedule
|
|
178
|
+
self._consolidation_task = asyncio.create_task(self._consolidation_loop())
|
|
179
|
+
logger.info(
|
|
180
|
+
f"TSDBConsolidationService started - Basic: {self._basic_interval}, Extensive: {self._extensive_interval}, Profound: {self._profound_interval}"
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
async def stop(self) -> None:
|
|
184
|
+
"""Stop the consolidation service gracefully."""
|
|
185
|
+
self._running = False
|
|
186
|
+
|
|
187
|
+
# Cancel any ongoing consolidation task
|
|
188
|
+
if self._consolidation_task and not self._consolidation_task.done():
|
|
189
|
+
logger.info("Cancelling ongoing consolidation task...")
|
|
190
|
+
self._consolidation_task.cancel()
|
|
191
|
+
try:
|
|
192
|
+
await self._consolidation_task
|
|
193
|
+
except asyncio.CancelledError:
|
|
194
|
+
logger.debug("Consolidation task cancelled successfully")
|
|
195
|
+
# Only re-raise if we're being cancelled ourselves
|
|
196
|
+
current = asyncio.current_task()
|
|
197
|
+
if current and current.cancelled():
|
|
198
|
+
raise
|
|
199
|
+
# Otherwise, this is a normal stop - don't propagate the cancellation
|
|
200
|
+
except Exception as e:
|
|
201
|
+
logger.error(f"Error cancelling consolidation task: {e}")
|
|
202
|
+
|
|
203
|
+
# Note: Final consolidation should be run explicitly by runtime BEFORE
|
|
204
|
+
# stopping services, not during stop() to avoid dependency issues
|
|
205
|
+
|
|
206
|
+
await super().stop()
|
|
207
|
+
logger.info("TSDBConsolidationService stopped")
|
|
208
|
+
|
|
209
|
+
async def _consolidation_loop(self) -> None:
|
|
210
|
+
"""
|
|
211
|
+
Main consolidation loop that runs every 6 hours.
|
|
212
|
+
|
|
213
|
+
The occurrence that wins the lock becomes "the consolidator" and runs:
|
|
214
|
+
1. Basic consolidation (always)
|
|
215
|
+
2. Extensive consolidation (if it's Monday)
|
|
216
|
+
3. Profound consolidation (if it's the 1st of the month)
|
|
217
|
+
|
|
218
|
+
This ensures only ONE occurrence handles all consolidation types sequentially,
|
|
219
|
+
preventing race conditions between consolidation levels.
|
|
220
|
+
"""
|
|
221
|
+
# First, consolidate any missed windows in the background
|
|
222
|
+
# This runs asynchronously and doesn't block the main init sequence
|
|
223
|
+
try:
|
|
224
|
+
await self._consolidate_missed_windows()
|
|
225
|
+
except Exception as e:
|
|
226
|
+
logger.error(f"Error consolidating missed windows: {e}", exc_info=True)
|
|
227
|
+
# Continue anyway - don't let missed window errors block regular operation
|
|
228
|
+
|
|
229
|
+
while self._running:
|
|
230
|
+
try:
|
|
231
|
+
# Calculate next run time
|
|
232
|
+
next_run = self._period_manager.get_next_period_start(self._now())
|
|
233
|
+
wait_seconds = (next_run - self._now()).total_seconds()
|
|
234
|
+
|
|
235
|
+
if wait_seconds > 0:
|
|
236
|
+
logger.info(f"Next consolidation at {next_run} ({wait_seconds:.0f}s)")
|
|
237
|
+
await asyncio.sleep(wait_seconds)
|
|
238
|
+
|
|
239
|
+
if self._running:
|
|
240
|
+
# Add random delay (30-600s) to prevent thundering herd
|
|
241
|
+
# when multiple instances start simultaneously
|
|
242
|
+
jitter_seconds = random.randint(30, 600)
|
|
243
|
+
logger.info(f"Adding random jitter delay of {jitter_seconds}s to prevent race conditions")
|
|
244
|
+
await asyncio.sleep(jitter_seconds)
|
|
245
|
+
|
|
246
|
+
if self._running:
|
|
247
|
+
# Run basic consolidation
|
|
248
|
+
# The occurrence that wins the lock becomes "the consolidator" for this run
|
|
249
|
+
await self._run_consolidation()
|
|
250
|
+
|
|
251
|
+
# If we're the consolidator, check if we should run extensive/profound
|
|
252
|
+
now = self._now()
|
|
253
|
+
|
|
254
|
+
# Check if it's Monday (0 = Monday) for extensive consolidation
|
|
255
|
+
if now.weekday() == 0:
|
|
256
|
+
logger.info("It's Monday - running extensive consolidation")
|
|
257
|
+
await self._run_extensive_consolidation()
|
|
258
|
+
|
|
259
|
+
# Check if it's the 1st of the month for profound consolidation
|
|
260
|
+
if now.day == 1:
|
|
261
|
+
logger.info("It's the 1st of the month - running profound consolidation")
|
|
262
|
+
self._run_profound_consolidation()
|
|
263
|
+
|
|
264
|
+
except asyncio.CancelledError:
|
|
265
|
+
logger.debug("Consolidation loop cancelled")
|
|
266
|
+
raise # Re-raise to properly exit the task
|
|
267
|
+
except Exception as e:
|
|
268
|
+
logger.error(f"Consolidation loop error: {e}", exc_info=True)
|
|
269
|
+
await asyncio.sleep(300) # 5 minutes
|
|
270
|
+
|
|
271
|
+
async def _run_consolidation(self) -> None:
|
|
272
|
+
"""Run a single consolidation cycle."""
|
|
273
|
+
consolidation_start = self._now()
|
|
274
|
+
total_records_processed = 0
|
|
275
|
+
total_summaries_created = 0
|
|
276
|
+
cleanup_stats = {"nodes_deleted": 0, "edges_deleted": 0}
|
|
277
|
+
|
|
278
|
+
try:
|
|
279
|
+
logger.info("=" * 60)
|
|
280
|
+
logger.info("Starting TSDB consolidation cycle")
|
|
281
|
+
logger.info(f"Started at: {consolidation_start.isoformat()}")
|
|
282
|
+
|
|
283
|
+
# Find periods that need consolidation
|
|
284
|
+
now = self._now()
|
|
285
|
+
cutoff_time = now - timedelta(hours=24)
|
|
286
|
+
|
|
287
|
+
# Get oldest unconsolidated data
|
|
288
|
+
oldest_data = self._find_oldest_unconsolidated_period()
|
|
289
|
+
if not oldest_data:
|
|
290
|
+
logger.info("No unconsolidated data found - nothing to consolidate")
|
|
291
|
+
return
|
|
292
|
+
|
|
293
|
+
logger.info(f"Oldest unconsolidated data from: {oldest_data.isoformat()}")
|
|
294
|
+
logger.info(f"Will consolidate up to: {cutoff_time.isoformat()}")
|
|
295
|
+
|
|
296
|
+
# Process periods
|
|
297
|
+
current_start, _ = self._period_manager.get_period_boundaries(oldest_data)
|
|
298
|
+
periods_consolidated = 0
|
|
299
|
+
max_periods = 30 # Limit per run
|
|
300
|
+
|
|
301
|
+
while current_start < cutoff_time and periods_consolidated < max_periods:
|
|
302
|
+
current_end = current_start + self._consolidation_interval
|
|
303
|
+
|
|
304
|
+
# Try to acquire lock for this period to prevent duplicate consolidation
|
|
305
|
+
lock_acquired = self._query_manager.acquire_period_lock(current_start)
|
|
306
|
+
|
|
307
|
+
if not lock_acquired:
|
|
308
|
+
logger.info(f"Period {current_start.isoformat()} is locked by another instance, skipping")
|
|
309
|
+
current_start = current_end
|
|
310
|
+
continue
|
|
311
|
+
|
|
312
|
+
try:
|
|
313
|
+
# Check if already consolidated (double-check after acquiring lock)
|
|
314
|
+
if not self._query_manager.check_period_consolidated(current_start):
|
|
315
|
+
period_start_time = self._now()
|
|
316
|
+
logger.info(f"Consolidating period: {current_start.isoformat()} to {current_end.isoformat()}")
|
|
317
|
+
|
|
318
|
+
# Count records in this period before consolidation
|
|
319
|
+
period_records = len(self._query_manager.query_all_nodes_in_period(current_start, current_end))
|
|
320
|
+
total_records_processed += period_records
|
|
321
|
+
|
|
322
|
+
summaries = await self._consolidate_period(current_start, current_end)
|
|
323
|
+
if summaries:
|
|
324
|
+
total_summaries_created += len(summaries)
|
|
325
|
+
period_duration = (self._now() - period_start_time).total_seconds()
|
|
326
|
+
logger.info(
|
|
327
|
+
f" ✓ Created {len(summaries)} summaries from {period_records} records in {period_duration:.2f}s"
|
|
328
|
+
)
|
|
329
|
+
periods_consolidated += 1
|
|
330
|
+
else:
|
|
331
|
+
logger.info(" - No summaries created for period (no data)")
|
|
332
|
+
else:
|
|
333
|
+
logger.info(f"Period {current_start.isoformat()} already consolidated by another instance")
|
|
334
|
+
|
|
335
|
+
finally:
|
|
336
|
+
# Always release the lock
|
|
337
|
+
self._query_manager.release_period_lock(current_start)
|
|
338
|
+
|
|
339
|
+
current_start = current_end
|
|
340
|
+
|
|
341
|
+
if periods_consolidated > 0:
|
|
342
|
+
logger.info(f"Consolidation complete: {periods_consolidated} periods processed")
|
|
343
|
+
logger.info(f" - Total records processed: {total_records_processed}")
|
|
344
|
+
logger.info(f" - Total summaries created: {total_summaries_created}")
|
|
345
|
+
if total_records_processed > 0:
|
|
346
|
+
compression_ratio = total_records_processed / max(total_summaries_created, 1)
|
|
347
|
+
logger.info(f" - Compression ratio: {compression_ratio:.1f}:1")
|
|
348
|
+
|
|
349
|
+
# Cleanup old data (run in thread to avoid blocking event loop)
|
|
350
|
+
cleanup_start = self._now()
|
|
351
|
+
logger.info("Starting cleanup of old consolidated data...")
|
|
352
|
+
# Count nodes before cleanup (logged later)
|
|
353
|
+
len(self._query_manager.query_all_nodes_in_period(now - timedelta(days=30), now))
|
|
354
|
+
|
|
355
|
+
# Run cleanup in thread executor to prevent Discord heartbeat blocking
|
|
356
|
+
loop = asyncio.get_event_loop()
|
|
357
|
+
nodes_deleted = await loop.run_in_executor(None, self._cleanup_old_data)
|
|
358
|
+
cleanup_stats["nodes_deleted"] = nodes_deleted
|
|
359
|
+
|
|
360
|
+
# Cleanup orphaned edges
|
|
361
|
+
edges_deleted = self._edge_manager.cleanup_orphaned_edges()
|
|
362
|
+
cleanup_stats["edges_deleted"] = edges_deleted
|
|
363
|
+
|
|
364
|
+
cleanup_duration = (self._now() - cleanup_start).total_seconds()
|
|
365
|
+
if nodes_deleted > 0 or edges_deleted > 0:
|
|
366
|
+
logger.info(f"Cleanup complete in {cleanup_duration:.2f}s:")
|
|
367
|
+
logger.info(f" - Nodes deleted: {nodes_deleted}")
|
|
368
|
+
logger.info(f" - Edges deleted: {edges_deleted}")
|
|
369
|
+
|
|
370
|
+
self._last_consolidation = now
|
|
371
|
+
|
|
372
|
+
# Final summary
|
|
373
|
+
total_duration = (self._now() - consolidation_start).total_seconds()
|
|
374
|
+
logger.info(f"TSDB consolidation cycle completed in {total_duration:.2f}s")
|
|
375
|
+
logger.info("=" * 60)
|
|
376
|
+
|
|
377
|
+
except Exception as e:
|
|
378
|
+
duration = (self._now() - consolidation_start).total_seconds()
|
|
379
|
+
logger.error(f"Consolidation failed after {duration:.2f}s: {e}", exc_info=True)
|
|
380
|
+
logger.error(f"Partial progress - Records: {total_records_processed}, Summaries: {total_summaries_created}")
|
|
381
|
+
|
|
382
|
+
async def _consolidate_missed_windows(self) -> None:
|
|
383
|
+
"""
|
|
384
|
+
Consolidate any missed windows since the last consolidation.
|
|
385
|
+
Called at startup to catch up on any periods missed while shutdown.
|
|
386
|
+
"""
|
|
387
|
+
try:
|
|
388
|
+
logger.info("Checking for missed consolidation windows...")
|
|
389
|
+
|
|
390
|
+
# Find the last consolidated period
|
|
391
|
+
last_consolidated = await self._query_manager.get_last_consolidated_period()
|
|
392
|
+
|
|
393
|
+
now = self._now()
|
|
394
|
+
cutoff_time = now - timedelta(hours=24) # Don't go back more than 24 hours
|
|
395
|
+
|
|
396
|
+
if last_consolidated:
|
|
397
|
+
# Start from the period after the last consolidated one
|
|
398
|
+
start_from = last_consolidated + self._consolidation_interval
|
|
399
|
+
logger.info(f"Last consolidated period: {last_consolidated}, starting from: {start_from}")
|
|
400
|
+
else:
|
|
401
|
+
# No previous consolidation found, check for oldest data
|
|
402
|
+
oldest_data = self._find_oldest_unconsolidated_period()
|
|
403
|
+
if not oldest_data:
|
|
404
|
+
logger.info("No unconsolidated data found")
|
|
405
|
+
return
|
|
406
|
+
|
|
407
|
+
# Start from the period containing the oldest data
|
|
408
|
+
start_from, _ = self._period_manager.get_period_boundaries(oldest_data)
|
|
409
|
+
logger.info(f"No previous consolidation found, starting from oldest data: {start_from}")
|
|
410
|
+
|
|
411
|
+
# Don't go back too far
|
|
412
|
+
if start_from < cutoff_time:
|
|
413
|
+
start_from = self._period_manager.get_period_start(cutoff_time)
|
|
414
|
+
logger.info(f"Limiting lookback to 24 hours, adjusted start: {start_from}")
|
|
415
|
+
|
|
416
|
+
# Process all missed periods up to the most recent completed period
|
|
417
|
+
current_period_start = self._period_manager.get_period_start(now)
|
|
418
|
+
periods_consolidated = 0
|
|
419
|
+
|
|
420
|
+
period_start = start_from
|
|
421
|
+
while period_start < current_period_start:
|
|
422
|
+
period_end = period_start + self._consolidation_interval
|
|
423
|
+
|
|
424
|
+
# Check if this period needs consolidation
|
|
425
|
+
if not self._query_manager.check_period_consolidated(period_start):
|
|
426
|
+
# Try to acquire lock for this period - only one occurrence should consolidate
|
|
427
|
+
lock_key = f"missed:{period_start.isoformat()}"
|
|
428
|
+
if self._query_manager._try_acquire_lock(lock_key, "missed", period_start.isoformat()):
|
|
429
|
+
logger.info(f"Acquired lock, consolidating missed period: {period_start} to {period_end}")
|
|
430
|
+
|
|
431
|
+
summaries = await self._consolidate_period(period_start, period_end)
|
|
432
|
+
if summaries:
|
|
433
|
+
logger.info(f"Created {len(summaries)} summaries for missed period {period_start}")
|
|
434
|
+
periods_consolidated += 1
|
|
435
|
+
else:
|
|
436
|
+
logger.debug(f"No data found for period {period_start}")
|
|
437
|
+
else:
|
|
438
|
+
logger.info(f"Another occurrence is consolidating period {period_start}, skipping")
|
|
439
|
+
else:
|
|
440
|
+
logger.debug(f"Period {period_start} already consolidated, checking edges...")
|
|
441
|
+
# Ensure edges exist for this already-consolidated period
|
|
442
|
+
await self._ensure_summary_edges(period_start, period_end)
|
|
443
|
+
|
|
444
|
+
# Move to next period
|
|
445
|
+
period_start = period_end
|
|
446
|
+
|
|
447
|
+
# Safety limit to prevent excessive processing
|
|
448
|
+
if periods_consolidated >= 10:
|
|
449
|
+
logger.warning("Reached limit of 10 periods in missed window consolidation")
|
|
450
|
+
break
|
|
451
|
+
|
|
452
|
+
if periods_consolidated > 0:
|
|
453
|
+
logger.info(f"Successfully consolidated {periods_consolidated} missed periods")
|
|
454
|
+
self._last_consolidation = now
|
|
455
|
+
else:
|
|
456
|
+
logger.info("No missed periods needed consolidation")
|
|
457
|
+
|
|
458
|
+
except Exception as e:
|
|
459
|
+
logger.error(f"Failed to consolidate missed windows: {e}", exc_info=True)
|
|
460
|
+
|
|
461
|
+
def _query_period_data(self, period_start: datetime, period_end: datetime) -> Tuple[
|
|
462
|
+
Dict[str, TSDBNodeQueryResult],
|
|
463
|
+
ServiceCorrelationQueryResult,
|
|
464
|
+
List[TaskCorrelationData],
|
|
465
|
+
]:
|
|
466
|
+
"""Query all data for a consolidation period."""
|
|
467
|
+
nodes_by_type = self._query_manager.query_all_nodes_in_period(period_start, period_end)
|
|
468
|
+
correlations = self._query_manager.query_service_correlations(period_start, period_end)
|
|
469
|
+
tasks = self._query_manager.query_tasks_in_period(period_start, period_end)
|
|
470
|
+
return nodes_by_type, correlations, tasks
|
|
471
|
+
|
|
472
|
+
async def _create_metric_summary(
|
|
473
|
+
self,
|
|
474
|
+
nodes_by_type: Dict[str, TSDBNodeQueryResult],
|
|
475
|
+
correlations: ServiceCorrelationQueryResult,
|
|
476
|
+
period_start: datetime,
|
|
477
|
+
period_end: datetime,
|
|
478
|
+
period_label: str,
|
|
479
|
+
converted_correlations: Dict[str, List[Union[MetricCorrelationData, ServiceInteractionData, TraceSpanData]]],
|
|
480
|
+
) -> Optional[GraphNode]:
|
|
481
|
+
"""Create metrics summary from TSDB nodes and correlations."""
|
|
482
|
+
tsdb_nodes = nodes_by_type.get(
|
|
483
|
+
"tsdb_data", TSDBNodeQueryResult(nodes=[], period_start=period_start, period_end=period_end)
|
|
484
|
+
).nodes
|
|
485
|
+
metric_correlations = correlations.metric_correlations
|
|
486
|
+
|
|
487
|
+
converted_correlations["metric_datapoint"] = list(metric_correlations)
|
|
488
|
+
|
|
489
|
+
if tsdb_nodes or metric_correlations:
|
|
490
|
+
return await self._metrics_consolidator.consolidate(
|
|
491
|
+
period_start, period_end, period_label, tsdb_nodes, metric_correlations
|
|
492
|
+
)
|
|
493
|
+
return None
|
|
494
|
+
|
|
495
|
+
async def _create_task_summary(
|
|
496
|
+
self,
|
|
497
|
+
tasks: List[TaskCorrelationData],
|
|
498
|
+
period_start: datetime,
|
|
499
|
+
period_end: datetime,
|
|
500
|
+
period_label: str,
|
|
501
|
+
) -> Optional[GraphNode]:
|
|
502
|
+
"""Create task summary from task correlations."""
|
|
503
|
+
if tasks:
|
|
504
|
+
return await self._task_consolidator.consolidate(period_start, period_end, period_label, tasks)
|
|
505
|
+
return None
|
|
506
|
+
|
|
507
|
+
async def _create_conversation_summary(
|
|
508
|
+
self,
|
|
509
|
+
correlations: ServiceCorrelationQueryResult,
|
|
510
|
+
period_start: datetime,
|
|
511
|
+
period_end: datetime,
|
|
512
|
+
period_label: str,
|
|
513
|
+
converted_correlations: Dict[str, List[Union[MetricCorrelationData, ServiceInteractionData, TraceSpanData]]],
|
|
514
|
+
) -> Optional[GraphNode]:
|
|
515
|
+
"""Create conversation summary with user participation edges."""
|
|
516
|
+
service_interactions = correlations.service_interactions
|
|
517
|
+
if not service_interactions:
|
|
518
|
+
return None
|
|
519
|
+
|
|
520
|
+
converted_correlations["service_interaction"] = list(service_interactions)
|
|
521
|
+
|
|
522
|
+
conversation_summary = await self._conversation_consolidator.consolidate(
|
|
523
|
+
period_start, period_end, period_label, service_interactions
|
|
524
|
+
)
|
|
525
|
+
if not conversation_summary:
|
|
526
|
+
return None
|
|
527
|
+
|
|
528
|
+
# Create user participation edges
|
|
529
|
+
participant_data = self._conversation_consolidator.get_participant_data(service_interactions)
|
|
530
|
+
if participant_data:
|
|
531
|
+
user_edges = self._edge_manager.create_user_participation_edges(
|
|
532
|
+
conversation_summary, participant_data, period_label
|
|
533
|
+
)
|
|
534
|
+
logger.info(f"Created {user_edges} user participation edges")
|
|
535
|
+
|
|
536
|
+
return conversation_summary
|
|
537
|
+
|
|
538
|
+
async def _create_trace_summary(
|
|
539
|
+
self,
|
|
540
|
+
correlations: ServiceCorrelationQueryResult,
|
|
541
|
+
period_start: datetime,
|
|
542
|
+
period_end: datetime,
|
|
543
|
+
period_label: str,
|
|
544
|
+
converted_correlations: Dict[str, List[Union[MetricCorrelationData, ServiceInteractionData, TraceSpanData]]],
|
|
545
|
+
) -> Optional[GraphNode]:
|
|
546
|
+
"""Create trace summary from trace spans."""
|
|
547
|
+
trace_spans = correlations.trace_spans
|
|
548
|
+
if not trace_spans:
|
|
549
|
+
return None
|
|
550
|
+
|
|
551
|
+
converted_correlations["trace_span"] = list(trace_spans)
|
|
552
|
+
return await self._trace_consolidator.consolidate(period_start, period_end, period_label, trace_spans)
|
|
553
|
+
|
|
554
|
+
async def _create_audit_summary(
|
|
555
|
+
self,
|
|
556
|
+
nodes_by_type: Dict[str, TSDBNodeQueryResult],
|
|
557
|
+
period_start: datetime,
|
|
558
|
+
period_end: datetime,
|
|
559
|
+
period_label: str,
|
|
560
|
+
) -> Optional[GraphNode]:
|
|
561
|
+
"""Create audit summary from audit nodes."""
|
|
562
|
+
audit_nodes = nodes_by_type.get(
|
|
563
|
+
"audit_entry", TSDBNodeQueryResult(nodes=[], period_start=period_start, period_end=period_end)
|
|
564
|
+
).nodes
|
|
565
|
+
if audit_nodes:
|
|
566
|
+
return await self._audit_consolidator.consolidate(period_start, period_end, period_label, audit_nodes)
|
|
567
|
+
return None
|
|
568
|
+
|
|
569
|
+
async def _consolidate_period(self, period_start: datetime, period_end: datetime) -> List[GraphNode]:
|
|
570
|
+
"""
|
|
571
|
+
Consolidate all data for a specific period.
|
|
572
|
+
|
|
573
|
+
This is the main consolidation logic that:
|
|
574
|
+
1. Queries all nodes and correlations
|
|
575
|
+
2. Creates summary nodes
|
|
576
|
+
3. Creates proper edges
|
|
577
|
+
|
|
578
|
+
Args:
|
|
579
|
+
period_start: Start of period
|
|
580
|
+
period_end: End of period
|
|
581
|
+
|
|
582
|
+
Returns:
|
|
583
|
+
List of created summary nodes
|
|
584
|
+
"""
|
|
585
|
+
period_label = self._period_manager.get_period_label(period_start)
|
|
586
|
+
summaries_created: List[GraphNode] = []
|
|
587
|
+
|
|
588
|
+
# 1. Query ALL data for the period
|
|
589
|
+
logger.info(f"Querying all data for period {period_label}")
|
|
590
|
+
nodes_by_type, correlations, tasks = self._query_period_data(period_start, period_end)
|
|
591
|
+
|
|
592
|
+
# 1.5. Handle consent expiry - anonymize expired TEMPORARY nodes
|
|
593
|
+
await self._handle_consent_expiry(nodes_by_type, period_end)
|
|
594
|
+
|
|
595
|
+
# 2. Create summaries
|
|
596
|
+
converted_correlations: Dict[str, List[Union[MetricCorrelationData, ServiceInteractionData, TraceSpanData]]] = (
|
|
597
|
+
{}
|
|
598
|
+
)
|
|
599
|
+
|
|
600
|
+
# Create each summary type using helper methods
|
|
601
|
+
metric_summary = await self._create_metric_summary(
|
|
602
|
+
nodes_by_type, correlations, period_start, period_end, period_label, converted_correlations
|
|
603
|
+
)
|
|
604
|
+
if metric_summary:
|
|
605
|
+
summaries_created.append(metric_summary)
|
|
606
|
+
|
|
607
|
+
task_summary = await self._create_task_summary(tasks, period_start, period_end, period_label)
|
|
608
|
+
if task_summary:
|
|
609
|
+
summaries_created.append(task_summary)
|
|
610
|
+
|
|
611
|
+
conversation_summary = await self._create_conversation_summary(
|
|
612
|
+
correlations, period_start, period_end, period_label, converted_correlations
|
|
613
|
+
)
|
|
614
|
+
if conversation_summary:
|
|
615
|
+
summaries_created.append(conversation_summary)
|
|
616
|
+
|
|
617
|
+
trace_summary = await self._create_trace_summary(
|
|
618
|
+
correlations, period_start, period_end, period_label, converted_correlations
|
|
619
|
+
)
|
|
620
|
+
if trace_summary:
|
|
621
|
+
summaries_created.append(trace_summary)
|
|
622
|
+
|
|
623
|
+
audit_summary = await self._create_audit_summary(nodes_by_type, period_start, period_end, period_label)
|
|
624
|
+
if audit_summary:
|
|
625
|
+
summaries_created.append(audit_summary)
|
|
626
|
+
|
|
627
|
+
# 3. Create edges
|
|
628
|
+
if summaries_created:
|
|
629
|
+
await self._create_all_edges(
|
|
630
|
+
summaries_created,
|
|
631
|
+
nodes_by_type,
|
|
632
|
+
converted_correlations,
|
|
633
|
+
tasks, # Use tasks directly (already TaskCorrelationData objects)
|
|
634
|
+
period_start,
|
|
635
|
+
period_label,
|
|
636
|
+
)
|
|
637
|
+
|
|
638
|
+
return summaries_created
|
|
639
|
+
|
|
640
|
+
def _get_consolidator_edges_for_summary(
|
|
641
|
+
self,
|
|
642
|
+
summary: GraphNode,
|
|
643
|
+
nodes_by_type: Dict[str, TSDBNodeQueryResult],
|
|
644
|
+
correlations: Dict[str, List[Union[MetricCorrelationData, ServiceInteractionData, TraceSpanData]]],
|
|
645
|
+
tasks: List[TaskCorrelationData],
|
|
646
|
+
period_start: datetime,
|
|
647
|
+
) -> List[Any]:
|
|
648
|
+
"""
|
|
649
|
+
Get edges from the appropriate consolidator based on summary type.
|
|
650
|
+
|
|
651
|
+
Args:
|
|
652
|
+
summary: Summary node
|
|
653
|
+
nodes_by_type: All nodes in the period by type
|
|
654
|
+
correlations: All correlations in the period by type
|
|
655
|
+
tasks: All tasks in the period
|
|
656
|
+
period_start: Start of the period
|
|
657
|
+
|
|
658
|
+
Returns:
|
|
659
|
+
List of edges from the consolidator
|
|
660
|
+
"""
|
|
661
|
+
if summary.type == NodeType.TSDB_SUMMARY:
|
|
662
|
+
tsdb_nodes = nodes_by_type.get(
|
|
663
|
+
"tsdb_data", TSDBNodeQueryResult(nodes=[], period_start=period_start, period_end=period_start)
|
|
664
|
+
).nodes
|
|
665
|
+
metric_correlations_raw = correlations.get("metric_datapoint", [])
|
|
666
|
+
metric_correlations = [c for c in metric_correlations_raw if isinstance(c, MetricCorrelationData)]
|
|
667
|
+
return self._metrics_consolidator.get_edges(summary, tsdb_nodes, metric_correlations)
|
|
668
|
+
|
|
669
|
+
elif summary.type == NodeType.CONVERSATION_SUMMARY:
|
|
670
|
+
service_interactions_raw = correlations.get("service_interaction", [])
|
|
671
|
+
service_interactions = [c for c in service_interactions_raw if isinstance(c, ServiceInteractionData)]
|
|
672
|
+
return self._conversation_consolidator.get_edges(summary, service_interactions)
|
|
673
|
+
|
|
674
|
+
elif summary.type == NodeType.TRACE_SUMMARY:
|
|
675
|
+
trace_spans_raw = correlations.get("trace_span", [])
|
|
676
|
+
trace_spans = [c for c in trace_spans_raw if isinstance(c, TraceSpanData)]
|
|
677
|
+
return self._trace_consolidator.get_edges(summary, trace_spans)
|
|
678
|
+
|
|
679
|
+
elif summary.type == NodeType.AUDIT_SUMMARY:
|
|
680
|
+
audit_nodes = nodes_by_type.get(
|
|
681
|
+
"audit_entry", TSDBNodeQueryResult(nodes=[], period_start=period_start, period_end=period_start)
|
|
682
|
+
).nodes
|
|
683
|
+
return self._audit_consolidator.get_edges(summary, audit_nodes)
|
|
684
|
+
|
|
685
|
+
elif summary.type == NodeType.TASK_SUMMARY:
|
|
686
|
+
return self._task_consolidator.get_edges(summary, tasks)
|
|
687
|
+
|
|
688
|
+
return []
|
|
689
|
+
|
|
690
|
+
async def _create_daily_summary_edges(
|
|
691
|
+
self,
|
|
692
|
+
summaries: List[GraphNode],
|
|
693
|
+
day: datetime,
|
|
694
|
+
) -> None:
|
|
695
|
+
"""
|
|
696
|
+
Create edges between daily summaries for the same day.
|
|
697
|
+
|
|
698
|
+
This method creates:
|
|
699
|
+
1. Cross-type edges (e.g., TSDB->Audit, Task->Trace) within the same day
|
|
700
|
+
2. Temporal edges to previous day's summaries
|
|
701
|
+
|
|
702
|
+
Args:
|
|
703
|
+
summaries: List of daily summary nodes
|
|
704
|
+
day: The date for these summaries
|
|
705
|
+
"""
|
|
706
|
+
if not summaries:
|
|
707
|
+
return
|
|
708
|
+
|
|
709
|
+
# Create cross-summary edges for same day
|
|
710
|
+
if len(summaries) > 1:
|
|
711
|
+
edges_created = self._edge_manager.create_cross_summary_edges(summaries, day)
|
|
712
|
+
logger.info(f"Created {edges_created} same-day edges for {day.date()}")
|
|
713
|
+
|
|
714
|
+
# Create temporal edges to previous day for each summary
|
|
715
|
+
for summary in summaries:
|
|
716
|
+
# Extract summary type from ID (e.g., "tsdb_summary_daily_20250715" -> "tsdb_summary")
|
|
717
|
+
parts = summary.id.split("_")
|
|
718
|
+
if len(parts) >= 3 and parts[2] == "daily":
|
|
719
|
+
summary_type = f"{parts[0]}_{parts[1]}"
|
|
720
|
+
# Previous day
|
|
721
|
+
previous_day = day - timedelta(days=1)
|
|
722
|
+
previous_id = f"{summary_type}_daily_{previous_day.strftime('%Y%m%d')}"
|
|
723
|
+
|
|
724
|
+
# Create temporal edges
|
|
725
|
+
created = self._edge_manager.create_temporal_edges(summary, previous_id)
|
|
726
|
+
if created:
|
|
727
|
+
logger.debug(f"Created {created} temporal edges from {summary.id} to {previous_id}")
|
|
728
|
+
|
|
729
|
+
def _create_consolidator_edges(
|
|
730
|
+
self,
|
|
731
|
+
summaries: List[GraphNode],
|
|
732
|
+
nodes_by_type: Dict[str, TSDBNodeQueryResult],
|
|
733
|
+
correlations: Dict[str, List[Union[MetricCorrelationData, ServiceInteractionData, TraceSpanData]]],
|
|
734
|
+
tasks: List[TaskCorrelationData],
|
|
735
|
+
period_start: datetime,
|
|
736
|
+
period_label: str,
|
|
737
|
+
) -> int:
|
|
738
|
+
"""Create type-specific edges from consolidators and memory edges."""
|
|
739
|
+
all_edges = []
|
|
740
|
+
|
|
741
|
+
# Collect edges from each consolidator based on summary type
|
|
742
|
+
for summary in summaries:
|
|
743
|
+
edges = self._get_consolidator_edges_for_summary(summary, nodes_by_type, correlations, tasks, period_start)
|
|
744
|
+
all_edges.extend(edges)
|
|
745
|
+
|
|
746
|
+
# Get memory edges (links from summaries to memory nodes)
|
|
747
|
+
nodes_by_type_dict = {node_type: result.nodes for node_type, result in nodes_by_type.items()}
|
|
748
|
+
memory_edges = self._memory_consolidator.consolidate(
|
|
749
|
+
period_start, period_start + self._consolidation_interval, period_label, nodes_by_type_dict, summaries
|
|
750
|
+
)
|
|
751
|
+
all_edges.extend(memory_edges)
|
|
752
|
+
|
|
753
|
+
# Create all edges in batch
|
|
754
|
+
if all_edges:
|
|
755
|
+
edges_created = self._edge_manager.create_edges(all_edges)
|
|
756
|
+
logger.info(f"Created {edges_created} edges for period {period_label}")
|
|
757
|
+
return edges_created
|
|
758
|
+
return 0
|
|
759
|
+
|
|
760
|
+
def _create_summarizes_edges(
|
|
761
|
+
self, summaries: List[GraphNode], nodes_by_type: Dict[str, TSDBNodeQueryResult], period_label: str
|
|
762
|
+
) -> int:
|
|
763
|
+
"""Create SUMMARIZES edges from primary summary to all nodes in period.
|
|
764
|
+
|
|
765
|
+
NOTE: Excludes tsdb_data nodes as they are raw telemetry that gets aggregated
|
|
766
|
+
into tsdb_summary. Creating edges from other summaries to tsdb_data would violate
|
|
767
|
+
the data hierarchy - tsdb_data is a different scope/layer than summaries.
|
|
768
|
+
|
|
769
|
+
Correct architecture:
|
|
770
|
+
- tsdb_data → aggregated into → tsdb_summary (via source_node_count)
|
|
771
|
+
- tsdb_summary ← TEMPORAL edges → other summaries (task_summary, etc.)
|
|
772
|
+
"""
|
|
773
|
+
# Collect all nodes in period (EXCLUDING tsdb_data - wrong scope/layer)
|
|
774
|
+
all_nodes_in_period = []
|
|
775
|
+
logger.debug(f"Collecting nodes for SUMMARIZES edges. nodes_by_type keys: {list(nodes_by_type.keys())}")
|
|
776
|
+
|
|
777
|
+
for node_type, result in nodes_by_type.items():
|
|
778
|
+
if node_type != "tsdb_data": # tsdb_data is different scope - only referenced by tsdb_summary
|
|
779
|
+
node_count = len(result.nodes) if hasattr(result, "nodes") else 0
|
|
780
|
+
logger.debug(f" {node_type}: {node_count} nodes")
|
|
781
|
+
if hasattr(result, "nodes"):
|
|
782
|
+
all_nodes_in_period.extend(result.nodes)
|
|
783
|
+
else:
|
|
784
|
+
logger.warning(f" {node_type} result has no 'nodes' attribute: {type(result)}")
|
|
785
|
+
|
|
786
|
+
logger.info(f"Total nodes collected for SUMMARIZES edges: {len(all_nodes_in_period)}")
|
|
787
|
+
|
|
788
|
+
if not all_nodes_in_period:
|
|
789
|
+
return 0
|
|
790
|
+
|
|
791
|
+
# Create a primary summary (TSDB or first available) to link all nodes
|
|
792
|
+
primary_summary = next(
|
|
793
|
+
(s for s in summaries if s.type == NodeType.TSDB_SUMMARY), summaries[0] if summaries else None
|
|
794
|
+
)
|
|
795
|
+
|
|
796
|
+
if not primary_summary:
|
|
797
|
+
return 0
|
|
798
|
+
|
|
799
|
+
logger.info(f"Creating edges from {primary_summary.id} to {len(all_nodes_in_period)} nodes in period")
|
|
800
|
+
edges_created = self._edge_manager.create_summary_to_nodes_edges(
|
|
801
|
+
primary_summary, all_nodes_in_period, "SUMMARIZES", f"Node active during {period_label}"
|
|
802
|
+
)
|
|
803
|
+
logger.info(f"Created {edges_created} SUMMARIZES edges for period {period_label}")
|
|
804
|
+
return edges_created
|
|
805
|
+
|
|
806
|
+
def _create_cross_summary_edges(self, summaries: List[GraphNode], period_start: datetime) -> int:
|
|
807
|
+
"""Create edges between summaries in the same period."""
|
|
808
|
+
if len(summaries) <= 1:
|
|
809
|
+
return 0
|
|
810
|
+
|
|
811
|
+
cross_edges = self._edge_manager.create_cross_summary_edges(summaries, period_start)
|
|
812
|
+
logger.info(f"Created {cross_edges} cross-summary edges")
|
|
813
|
+
return cross_edges
|
|
814
|
+
|
|
815
|
+
def _create_temporal_edges(self, summaries: List[GraphNode], period_start: datetime) -> int:
|
|
816
|
+
"""Create temporal edges to previous and next period summaries."""
|
|
817
|
+
total_created = 0
|
|
818
|
+
|
|
819
|
+
# Link to previous period summaries
|
|
820
|
+
for summary in summaries:
|
|
821
|
+
# Extract summary type prefix (e.g., "tsdb_summary" or "tsdb_summary_daily")
|
|
822
|
+
summary_type = summary.id.split("_")[0] + "_" + summary.id.split("_")[1]
|
|
823
|
+
|
|
824
|
+
# Find the most recent previous summary (handles gaps in timeline)
|
|
825
|
+
previous_id = self._edge_manager.get_previous_summary_id(summary_type, summary.id)
|
|
826
|
+
|
|
827
|
+
if previous_id:
|
|
828
|
+
created = self._edge_manager.create_temporal_edges(summary, previous_id)
|
|
829
|
+
if created:
|
|
830
|
+
logger.debug(f"Created {created} temporal edges for {summary.id}")
|
|
831
|
+
total_created += created
|
|
832
|
+
else:
|
|
833
|
+
logger.debug(f"No previous summary found for {summary.id} - this may be the first summary")
|
|
834
|
+
|
|
835
|
+
# Link to next period summaries
|
|
836
|
+
edges_to_next = self._edge_manager.update_next_period_edges(period_start, summaries)
|
|
837
|
+
if edges_to_next > 0:
|
|
838
|
+
logger.info(f"Created {edges_to_next} edges to next period summaries")
|
|
839
|
+
total_created += edges_to_next
|
|
840
|
+
|
|
841
|
+
return total_created
|
|
842
|
+
|
|
843
|
+
async def _create_all_edges(
|
|
844
|
+
self,
|
|
845
|
+
summaries: List[GraphNode],
|
|
846
|
+
nodes_by_type: Dict[str, TSDBNodeQueryResult],
|
|
847
|
+
correlations: Dict[str, List[Union[MetricCorrelationData, ServiceInteractionData, TraceSpanData]]],
|
|
848
|
+
tasks: List[TaskCorrelationData],
|
|
849
|
+
period_start: datetime,
|
|
850
|
+
period_label: str,
|
|
851
|
+
) -> None:
|
|
852
|
+
"""
|
|
853
|
+
Create all necessary edges for the summaries.
|
|
854
|
+
|
|
855
|
+
This orchestrates creation of:
|
|
856
|
+
1. Type-specific edges from consolidators
|
|
857
|
+
2. SUMMARIZES edges to all nodes in period
|
|
858
|
+
3. Cross-summary edges within same period
|
|
859
|
+
4. Temporal edges to previous/next periods
|
|
860
|
+
|
|
861
|
+
Args:
|
|
862
|
+
summaries: List of summary nodes created
|
|
863
|
+
nodes_by_type: All nodes in the period by type
|
|
864
|
+
correlations: All correlations in the period by type
|
|
865
|
+
tasks: All tasks in the period
|
|
866
|
+
period_start: Start of the period
|
|
867
|
+
period_label: Human-readable period label
|
|
868
|
+
"""
|
|
869
|
+
# Create type-specific and memory edges
|
|
870
|
+
self._create_consolidator_edges(summaries, nodes_by_type, correlations, tasks, period_start, period_label)
|
|
871
|
+
|
|
872
|
+
# Create SUMMARIZES edges to all nodes
|
|
873
|
+
self._create_summarizes_edges(summaries, nodes_by_type, period_label)
|
|
874
|
+
|
|
875
|
+
# Create cross-summary edges
|
|
876
|
+
self._create_cross_summary_edges(summaries, period_start)
|
|
877
|
+
|
|
878
|
+
# Create temporal edges
|
|
879
|
+
self._create_temporal_edges(summaries, period_start)
|
|
880
|
+
|
|
881
|
+
def _find_oldest_unconsolidated_period(self) -> Optional[datetime]:
|
|
882
|
+
"""Find the oldest data that needs consolidation."""
|
|
883
|
+
from ciris_engine.logic.persistence.db.core import get_db_connection
|
|
884
|
+
from ciris_engine.logic.services.graph.tsdb_consolidation.sql_builders import parse_datetime_field
|
|
885
|
+
|
|
886
|
+
try:
|
|
887
|
+
with get_db_connection(db_path=self.db_path) as conn:
|
|
888
|
+
cursor = conn.cursor()
|
|
889
|
+
|
|
890
|
+
# Check for oldest TSDB data
|
|
891
|
+
cursor.execute(
|
|
892
|
+
"""
|
|
893
|
+
SELECT MIN(created_at) as oldest
|
|
894
|
+
FROM graph_nodes
|
|
895
|
+
WHERE node_type = 'tsdb_data'
|
|
896
|
+
"""
|
|
897
|
+
)
|
|
898
|
+
row = cursor.fetchone()
|
|
899
|
+
|
|
900
|
+
if row and row["oldest"]:
|
|
901
|
+
oldest_tsdb = parse_datetime_field(row["oldest"])
|
|
902
|
+
if oldest_tsdb:
|
|
903
|
+
return oldest_tsdb
|
|
904
|
+
|
|
905
|
+
# Check for oldest correlation
|
|
906
|
+
cursor.execute(
|
|
907
|
+
"""
|
|
908
|
+
SELECT MIN(timestamp) as oldest
|
|
909
|
+
FROM service_correlations
|
|
910
|
+
"""
|
|
911
|
+
)
|
|
912
|
+
row = cursor.fetchone()
|
|
913
|
+
|
|
914
|
+
if row and row["oldest"]:
|
|
915
|
+
oldest_correlation = parse_datetime_field(row["oldest"])
|
|
916
|
+
if oldest_correlation:
|
|
917
|
+
return oldest_correlation
|
|
918
|
+
|
|
919
|
+
except Exception as e:
|
|
920
|
+
logger.error(f"Failed to find oldest data: {e}")
|
|
921
|
+
|
|
922
|
+
return None
|
|
923
|
+
|
|
924
|
+
def _cleanup_old_data(self) -> int:
|
|
925
|
+
"""
|
|
926
|
+
Clean up old consolidated data that has been successfully summarized.
|
|
927
|
+
|
|
928
|
+
IMPORTANT: This method NEVER touches the audit_log table.
|
|
929
|
+
Audit entries are preserved forever for absolute reputability.
|
|
930
|
+
Only graph node representations are cleaned up.
|
|
931
|
+
"""
|
|
932
|
+
try:
|
|
933
|
+
from ciris_engine.logic.persistence.db.core import get_db_connection
|
|
934
|
+
from ciris_engine.logic.services.graph.tsdb_consolidation.cleanup_helpers import (
|
|
935
|
+
cleanup_audit_summary,
|
|
936
|
+
cleanup_trace_summary,
|
|
937
|
+
cleanup_tsdb_summary,
|
|
938
|
+
)
|
|
939
|
+
from ciris_engine.logic.services.graph.tsdb_consolidation.date_calculation_helpers import (
|
|
940
|
+
get_retention_cutoff_date,
|
|
941
|
+
)
|
|
942
|
+
from ciris_engine.logic.services.graph.tsdb_consolidation.db_query_helpers import query_expired_summaries
|
|
943
|
+
|
|
944
|
+
logger.info("Starting cleanup of consolidated graph data (audit_log untouched)")
|
|
945
|
+
|
|
946
|
+
# Connect to database using get_db_connection (supports both SQLite and PostgreSQL)
|
|
947
|
+
conn = get_db_connection(db_path=self.db_path)
|
|
948
|
+
cursor = conn.cursor()
|
|
949
|
+
|
|
950
|
+
# Find all summaries older than retention period
|
|
951
|
+
retention_cutoff = get_retention_cutoff_date(self._now(), int(self._raw_retention.total_seconds() / 3600))
|
|
952
|
+
summaries = query_expired_summaries(cursor, retention_cutoff)
|
|
953
|
+
|
|
954
|
+
total_deleted = 0
|
|
955
|
+
|
|
956
|
+
# Process each expired summary
|
|
957
|
+
for node_id, node_type, attrs_json in summaries:
|
|
958
|
+
deleted = 0
|
|
959
|
+
|
|
960
|
+
if node_type == "tsdb_summary":
|
|
961
|
+
deleted = cleanup_tsdb_summary(cursor, node_id, attrs_json)
|
|
962
|
+
elif node_type == "audit_summary":
|
|
963
|
+
deleted = cleanup_audit_summary(cursor, node_id, attrs_json)
|
|
964
|
+
elif node_type == "trace_summary":
|
|
965
|
+
deleted = cleanup_trace_summary(cursor, node_id, attrs_json)
|
|
966
|
+
|
|
967
|
+
total_deleted += deleted
|
|
968
|
+
|
|
969
|
+
# Commit changes
|
|
970
|
+
if total_deleted > 0:
|
|
971
|
+
conn.commit()
|
|
972
|
+
logger.info(f"Cleanup complete: deleted {total_deleted} total records")
|
|
973
|
+
else:
|
|
974
|
+
logger.info("No data to cleanup")
|
|
975
|
+
|
|
976
|
+
conn.close()
|
|
977
|
+
return total_deleted
|
|
978
|
+
|
|
979
|
+
except Exception as e:
|
|
980
|
+
logger.error(f"Error during cleanup: {e}", exc_info=True)
|
|
981
|
+
return 0
|
|
982
|
+
|
|
983
|
+
async def is_healthy(self) -> bool:
|
|
984
|
+
"""Check if the service is healthy.
|
|
985
|
+
|
|
986
|
+
The service is healthy if:
|
|
987
|
+
- It's running
|
|
988
|
+
- Memory bus is available
|
|
989
|
+
|
|
990
|
+
Note: We don't check consolidation_task state because the task may
|
|
991
|
+
complete between consolidation windows and that's normal behavior.
|
|
992
|
+
"""
|
|
993
|
+
return self._running and self._memory_bus is not None
|
|
994
|
+
|
|
995
|
+
def get_capabilities(self) -> ServiceCapabilities:
|
|
996
|
+
"""Get service capabilities."""
|
|
997
|
+
return ServiceCapabilities(
|
|
998
|
+
service_name="TSDBConsolidationService",
|
|
999
|
+
actions=[
|
|
1000
|
+
"consolidate_tsdb_nodes",
|
|
1001
|
+
"consolidate_all_data",
|
|
1002
|
+
"create_proper_edges",
|
|
1003
|
+
"track_memory_events",
|
|
1004
|
+
"summarize_tasks",
|
|
1005
|
+
"create_6hour_summaries",
|
|
1006
|
+
],
|
|
1007
|
+
version="2.0.0",
|
|
1008
|
+
dependencies=["MemoryService", "TimeService"],
|
|
1009
|
+
metadata=None,
|
|
1010
|
+
)
|
|
1011
|
+
|
|
1012
|
+
def get_status(self) -> ServiceStatus:
|
|
1013
|
+
"""Get service status."""
|
|
1014
|
+
current_time = self._now()
|
|
1015
|
+
uptime_seconds = 0.0
|
|
1016
|
+
if self._start_time:
|
|
1017
|
+
uptime_seconds = (current_time - self._start_time).total_seconds()
|
|
1018
|
+
|
|
1019
|
+
return ServiceStatus(
|
|
1020
|
+
service_name="TSDBConsolidationService",
|
|
1021
|
+
service_type="graph_service",
|
|
1022
|
+
is_healthy=self._running and self._memory_bus is not None,
|
|
1023
|
+
uptime_seconds=uptime_seconds,
|
|
1024
|
+
metrics={
|
|
1025
|
+
"last_consolidation_timestamp": (
|
|
1026
|
+
self._last_consolidation.timestamp() if self._last_consolidation else 0.0
|
|
1027
|
+
),
|
|
1028
|
+
"task_running": 1.0 if (self._consolidation_task and not self._consolidation_task.done()) else 0.0,
|
|
1029
|
+
"last_basic_consolidation": self._last_consolidation.timestamp() if self._last_consolidation else 0.0,
|
|
1030
|
+
"last_extensive_consolidation": (
|
|
1031
|
+
self._last_extensive_consolidation.timestamp() if self._last_extensive_consolidation else 0.0
|
|
1032
|
+
),
|
|
1033
|
+
"last_profound_consolidation": (
|
|
1034
|
+
self._last_profound_consolidation.timestamp() if self._last_profound_consolidation else 0.0
|
|
1035
|
+
),
|
|
1036
|
+
"consolidation_task_running": (
|
|
1037
|
+
1.0 if (self._consolidation_task and not self._consolidation_task.done()) else 0.0
|
|
1038
|
+
),
|
|
1039
|
+
},
|
|
1040
|
+
last_error=None,
|
|
1041
|
+
last_health_check=current_time,
|
|
1042
|
+
custom_metrics={
|
|
1043
|
+
"basic_interval_hours": self._basic_interval.total_seconds() / 3600,
|
|
1044
|
+
"extensive_interval_days": self._extensive_interval.total_seconds() / 86400,
|
|
1045
|
+
"profound_interval_days": self._profound_interval.total_seconds() / 86400,
|
|
1046
|
+
"profound_target_mb_per_day": self._profound_target_mb_per_day,
|
|
1047
|
+
},
|
|
1048
|
+
)
|
|
1049
|
+
|
|
1050
|
+
def get_node_type(self) -> NodeType:
|
|
1051
|
+
"""Get the node type this service manages."""
|
|
1052
|
+
return NodeType.TSDB_SUMMARY
|
|
1053
|
+
|
|
1054
|
+
def _is_period_consolidated(self, period_start: datetime, period_end: datetime) -> bool:
|
|
1055
|
+
"""Check if a period has already been consolidated."""
|
|
1056
|
+
try:
|
|
1057
|
+
# Query for existing TSDB summary for this exact period
|
|
1058
|
+
# Use direct DB query since MemoryQuery doesn't support field conditions
|
|
1059
|
+
from ciris_engine.logic.persistence.db.core import get_db_connection
|
|
1060
|
+
from ciris_engine.logic.persistence.db.dialect import get_adapter
|
|
1061
|
+
|
|
1062
|
+
adapter = get_adapter()
|
|
1063
|
+
conn = get_db_connection(db_path=self.db_path)
|
|
1064
|
+
cursor = conn.cursor()
|
|
1065
|
+
|
|
1066
|
+
# Query for TSDB summaries with matching period (PostgreSQL: JSONB operators, SQLite: json_extract)
|
|
1067
|
+
if adapter.is_postgresql():
|
|
1068
|
+
sql = """
|
|
1069
|
+
SELECT COUNT(*) FROM graph_nodes
|
|
1070
|
+
WHERE node_type = ?
|
|
1071
|
+
AND attributes_json->>'period_start' = ?
|
|
1072
|
+
AND attributes_json->>'period_end' = ?
|
|
1073
|
+
"""
|
|
1074
|
+
else:
|
|
1075
|
+
sql = """
|
|
1076
|
+
SELECT COUNT(*) FROM graph_nodes
|
|
1077
|
+
WHERE node_type = ?
|
|
1078
|
+
AND json_extract(attributes_json, '$.period_start') = ?
|
|
1079
|
+
AND json_extract(attributes_json, '$.period_end') = ?
|
|
1080
|
+
"""
|
|
1081
|
+
|
|
1082
|
+
cursor.execute(
|
|
1083
|
+
sql,
|
|
1084
|
+
(NodeType.TSDB_SUMMARY.value, period_start.isoformat(), period_end.isoformat()),
|
|
1085
|
+
)
|
|
1086
|
+
|
|
1087
|
+
result = cursor.fetchone()
|
|
1088
|
+
count = int(result[0]) if result else 0
|
|
1089
|
+
conn.close()
|
|
1090
|
+
|
|
1091
|
+
return count > 0
|
|
1092
|
+
except Exception as e:
|
|
1093
|
+
logger.error(f"Error checking if period consolidated: {e}")
|
|
1094
|
+
return False
|
|
1095
|
+
|
|
1096
|
+
async def _ensure_summary_edges(self, period_start: datetime, period_end: datetime) -> None:
|
|
1097
|
+
"""
|
|
1098
|
+
Ensure edges exist for an already-consolidated period.
|
|
1099
|
+
This fixes the issue where summaries exist but have no SUMMARIZES edges.
|
|
1100
|
+
|
|
1101
|
+
Args:
|
|
1102
|
+
period_start: Start of the period
|
|
1103
|
+
period_end: End of the period
|
|
1104
|
+
"""
|
|
1105
|
+
try:
|
|
1106
|
+
period_label = self._period_manager.get_period_label(period_start)
|
|
1107
|
+
logger.info(f"Ensuring edges exist for consolidated period {period_label}")
|
|
1108
|
+
|
|
1109
|
+
# Find the summary node for this period
|
|
1110
|
+
period_id = period_start.strftime("%Y%m%d_%H")
|
|
1111
|
+
summary_id = f"tsdb_summary_{period_id}"
|
|
1112
|
+
|
|
1113
|
+
# Check if SUMMARIZES edges already exist
|
|
1114
|
+
from ciris_engine.logic.persistence.db.core import get_db_connection
|
|
1115
|
+
|
|
1116
|
+
with get_db_connection(db_path=self.db_path) as conn:
|
|
1117
|
+
cursor = conn.cursor()
|
|
1118
|
+
cursor.execute(
|
|
1119
|
+
"""
|
|
1120
|
+
SELECT COUNT(*) as count
|
|
1121
|
+
FROM graph_edges
|
|
1122
|
+
WHERE source_node_id = ?
|
|
1123
|
+
AND relationship = 'SUMMARIZES'
|
|
1124
|
+
""",
|
|
1125
|
+
(summary_id,),
|
|
1126
|
+
)
|
|
1127
|
+
|
|
1128
|
+
edge_count = cursor.fetchone()["count"]
|
|
1129
|
+
|
|
1130
|
+
if edge_count > 0:
|
|
1131
|
+
logger.debug(f"Period {period_label} already has {edge_count} SUMMARIZES edges")
|
|
1132
|
+
return
|
|
1133
|
+
|
|
1134
|
+
# No SUMMARIZES edges exist - we need to create them
|
|
1135
|
+
logger.warning(f"Period {period_label} has NO SUMMARIZES edges! Creating them now...")
|
|
1136
|
+
|
|
1137
|
+
# Query all nodes in the period
|
|
1138
|
+
nodes_by_type = self._query_manager.query_all_nodes_in_period(period_start, period_end)
|
|
1139
|
+
|
|
1140
|
+
# Get the summary node
|
|
1141
|
+
from ciris_engine.schemas.services.graph_core import GraphNode, GraphScope, NodeType
|
|
1142
|
+
|
|
1143
|
+
summary_node = GraphNode(
|
|
1144
|
+
id=summary_id,
|
|
1145
|
+
type=NodeType.TSDB_SUMMARY,
|
|
1146
|
+
scope=GraphScope.LOCAL,
|
|
1147
|
+
attributes={},
|
|
1148
|
+
updated_by="tsdb_consolidation",
|
|
1149
|
+
updated_at=period_end,
|
|
1150
|
+
)
|
|
1151
|
+
|
|
1152
|
+
# Collect all nodes (except tsdb_data)
|
|
1153
|
+
all_nodes_in_period = []
|
|
1154
|
+
for node_type, result in nodes_by_type.items():
|
|
1155
|
+
if node_type != "tsdb_data" and hasattr(result, "nodes"):
|
|
1156
|
+
all_nodes_in_period.extend(result.nodes)
|
|
1157
|
+
|
|
1158
|
+
if all_nodes_in_period:
|
|
1159
|
+
logger.info(f"Creating SUMMARIZES edges from {summary_id} to {len(all_nodes_in_period)} nodes")
|
|
1160
|
+
edges_created = self._edge_manager.create_summary_to_nodes_edges(
|
|
1161
|
+
summary_node, all_nodes_in_period, "SUMMARIZES", f"Node active during {period_label}"
|
|
1162
|
+
)
|
|
1163
|
+
logger.info(f"Created {edges_created} SUMMARIZES edges for period {period_label}")
|
|
1164
|
+
else:
|
|
1165
|
+
logger.warning(f"No nodes found in period {period_label} to create edges to")
|
|
1166
|
+
|
|
1167
|
+
except Exception as e:
|
|
1168
|
+
logger.error(f"Error ensuring summary edges: {e}", exc_info=True)
|
|
1169
|
+
|
|
1170
|
+
def _calculate_next_run_time(self) -> datetime:
|
|
1171
|
+
"""Calculate when the next consolidation should run."""
|
|
1172
|
+
# Run at the start of the next 6-hour period
|
|
1173
|
+
current_time = self._now()
|
|
1174
|
+
hours_since_epoch = current_time.timestamp() / 3600
|
|
1175
|
+
periods_since_epoch = int(hours_since_epoch / 6)
|
|
1176
|
+
next_period = periods_since_epoch + 1
|
|
1177
|
+
next_run_timestamp = next_period * 6 * 3600
|
|
1178
|
+
return datetime.fromtimestamp(next_run_timestamp, tz=timezone.utc)
|
|
1179
|
+
|
|
1180
|
+
def _calculate_next_period_start(self, interval: timedelta) -> datetime:
|
|
1181
|
+
"""Calculate the next period start for a given interval."""
|
|
1182
|
+
current_time = self._now()
|
|
1183
|
+
seconds_since_epoch = current_time.timestamp()
|
|
1184
|
+
interval_seconds = interval.total_seconds()
|
|
1185
|
+
periods_since_epoch = int(seconds_since_epoch / interval_seconds)
|
|
1186
|
+
next_period = periods_since_epoch + 1
|
|
1187
|
+
next_run_timestamp = next_period * interval_seconds
|
|
1188
|
+
return datetime.fromtimestamp(next_run_timestamp, tz=timezone.utc)
|
|
1189
|
+
|
|
1190
|
+
def _get_next_weekly_monday(self) -> datetime:
|
|
1191
|
+
"""Get next Monday at 00:00 UTC for weekly consolidation."""
|
|
1192
|
+
now = self._now()
|
|
1193
|
+
days_until_monday = (7 - now.weekday()) % 7
|
|
1194
|
+
|
|
1195
|
+
# If it's Monday but past midnight, schedule for next Monday
|
|
1196
|
+
if days_until_monday == 0 and now.hour > 0:
|
|
1197
|
+
days_until_monday = 7
|
|
1198
|
+
|
|
1199
|
+
next_monday = now.date() + timedelta(days=days_until_monday)
|
|
1200
|
+
return datetime.combine(next_monday, datetime.min.time(), tzinfo=timezone.utc)
|
|
1201
|
+
|
|
1202
|
+
def _get_next_month_start(self) -> datetime:
|
|
1203
|
+
"""Get first day of next month at 00:00 UTC for monthly consolidation."""
|
|
1204
|
+
now = self._now()
|
|
1205
|
+
|
|
1206
|
+
# If it's the 1st at exactly 00:00, run now
|
|
1207
|
+
if now.day == 1 and now.hour == 0 and now.minute == 0:
|
|
1208
|
+
return now.replace(second=0, microsecond=0)
|
|
1209
|
+
|
|
1210
|
+
# Otherwise, calculate first day of next month
|
|
1211
|
+
if now.month == 12:
|
|
1212
|
+
next_month_date = now.replace(year=now.year + 1, month=1, day=1)
|
|
1213
|
+
else:
|
|
1214
|
+
next_month_date = now.replace(month=now.month + 1, day=1)
|
|
1215
|
+
|
|
1216
|
+
return next_month_date.replace(hour=0, minute=0, second=0, microsecond=0)
|
|
1217
|
+
|
|
1218
|
+
def _cleanup_old_nodes(self) -> int:
|
|
1219
|
+
"""Legacy method name - calls _cleanup_old_data."""
|
|
1220
|
+
result = self._cleanup_old_data()
|
|
1221
|
+
return result if result is not None else 0
|
|
1222
|
+
|
|
1223
|
+
def get_summary_for_period(self, period_start: datetime, period_end: datetime) -> Optional[TSDBPeriodSummary]:
|
|
1224
|
+
"""Get the summary for a specific period."""
|
|
1225
|
+
try:
|
|
1226
|
+
# Use direct DB query since MemoryQuery doesn't support field conditions
|
|
1227
|
+
from ciris_engine.logic.persistence.db.core import get_db_connection
|
|
1228
|
+
from ciris_engine.logic.persistence.db.dialect import get_adapter
|
|
1229
|
+
|
|
1230
|
+
adapter = get_adapter()
|
|
1231
|
+
conn = get_db_connection(db_path=self.db_path)
|
|
1232
|
+
cursor = conn.cursor()
|
|
1233
|
+
|
|
1234
|
+
# Query for TSDB summaries with matching period (PostgreSQL: JSONB operators, SQLite: json_extract)
|
|
1235
|
+
if adapter.is_postgresql():
|
|
1236
|
+
sql = """
|
|
1237
|
+
SELECT attributes_json FROM graph_nodes
|
|
1238
|
+
WHERE node_type = ?
|
|
1239
|
+
AND attributes_json->>'period_start' = ?
|
|
1240
|
+
AND attributes_json->>'period_end' = ?
|
|
1241
|
+
LIMIT 1
|
|
1242
|
+
"""
|
|
1243
|
+
else:
|
|
1244
|
+
sql = """
|
|
1245
|
+
SELECT attributes_json FROM graph_nodes
|
|
1246
|
+
WHERE node_type = ?
|
|
1247
|
+
AND json_extract(attributes_json, '$.period_start') = ?
|
|
1248
|
+
AND json_extract(attributes_json, '$.period_end') = ?
|
|
1249
|
+
LIMIT 1
|
|
1250
|
+
"""
|
|
1251
|
+
|
|
1252
|
+
cursor.execute(
|
|
1253
|
+
sql,
|
|
1254
|
+
(NodeType.TSDB_SUMMARY.value, period_start.isoformat(), period_end.isoformat()),
|
|
1255
|
+
)
|
|
1256
|
+
|
|
1257
|
+
row = cursor.fetchone()
|
|
1258
|
+
conn.close()
|
|
1259
|
+
|
|
1260
|
+
if row:
|
|
1261
|
+
# Parse the node data
|
|
1262
|
+
import json
|
|
1263
|
+
|
|
1264
|
+
node_data = json.loads(row[0])
|
|
1265
|
+
attrs = node_data.get("attributes", {})
|
|
1266
|
+
# Return the summary data as a typed schema
|
|
1267
|
+
return TSDBPeriodSummary(
|
|
1268
|
+
metrics=attrs.get("metrics", {}),
|
|
1269
|
+
total_tokens=attrs.get("total_tokens", 0),
|
|
1270
|
+
total_cost_cents=attrs.get("total_cost_cents", 0),
|
|
1271
|
+
total_carbon_grams=attrs.get("total_carbon_grams", 0),
|
|
1272
|
+
total_energy_kwh=attrs.get("total_energy_kwh", 0),
|
|
1273
|
+
action_counts=attrs.get("action_counts", {}),
|
|
1274
|
+
source_node_count=attrs.get("source_node_count", 0),
|
|
1275
|
+
period_start=attrs.get("period_start", period_start.isoformat()),
|
|
1276
|
+
period_end=attrs.get("period_end", period_end.isoformat()),
|
|
1277
|
+
period_label=attrs.get("period_label", ""),
|
|
1278
|
+
conversations=attrs.get("conversations", []),
|
|
1279
|
+
traces=attrs.get("traces", []),
|
|
1280
|
+
audits=attrs.get("audits", []),
|
|
1281
|
+
tasks=attrs.get("tasks", []),
|
|
1282
|
+
memories=attrs.get("memories", []),
|
|
1283
|
+
)
|
|
1284
|
+
return None
|
|
1285
|
+
except Exception as e:
|
|
1286
|
+
logger.error(f"Error getting summary for period: {e}")
|
|
1287
|
+
return None
|
|
1288
|
+
|
|
1289
|
+
def get_service_type(self) -> ServiceType:
|
|
1290
|
+
"""Get the service type."""
|
|
1291
|
+
return ServiceType.TELEMETRY
|
|
1292
|
+
|
|
1293
|
+
def _should_anonymize_node(self, node: GraphNode, period_end: datetime) -> bool:
|
|
1294
|
+
"""Check if a node should be anonymized due to consent expiry."""
|
|
1295
|
+
if not hasattr(node, "consent_stream") or not hasattr(node, "expires_at"):
|
|
1296
|
+
return False
|
|
1297
|
+
|
|
1298
|
+
if node.consent_stream != ConsentStream.TEMPORARY or not node.expires_at:
|
|
1299
|
+
return False
|
|
1300
|
+
|
|
1301
|
+
return period_end > node.expires_at
|
|
1302
|
+
|
|
1303
|
+
def _generate_anonymized_id(self, original_id: str, consent_stream: ConsentStream) -> str:
|
|
1304
|
+
"""Generate anonymized ID based on hash of original ID."""
|
|
1305
|
+
import hashlib
|
|
1306
|
+
|
|
1307
|
+
id_hash = hashlib.sha256(original_id.encode()).hexdigest()[:8]
|
|
1308
|
+
|
|
1309
|
+
if consent_stream == ConsentStream.TEMPORARY:
|
|
1310
|
+
return f"temporary_user_{id_hash}"
|
|
1311
|
+
else:
|
|
1312
|
+
return f"anonymous_user_{id_hash}"
|
|
1313
|
+
|
|
1314
|
+
def _remove_pii_from_attributes(self, node: GraphNode, period_end: datetime) -> None:
|
|
1315
|
+
"""Remove PII fields from node attributes and add anonymization metadata."""
|
|
1316
|
+
if not hasattr(node, "attributes") or not isinstance(node.attributes, dict):
|
|
1317
|
+
return
|
|
1318
|
+
|
|
1319
|
+
# Remove PII fields
|
|
1320
|
+
pii_fields = ["email", "name", "phone", "address", "ip_address"]
|
|
1321
|
+
for field in pii_fields:
|
|
1322
|
+
if field in node.attributes:
|
|
1323
|
+
del node.attributes[field]
|
|
1324
|
+
|
|
1325
|
+
# Add anonymization metadata
|
|
1326
|
+
node.attributes["anonymized_at"] = period_end.isoformat()
|
|
1327
|
+
node.attributes["original_stream"] = node.consent_stream
|
|
1328
|
+
|
|
1329
|
+
async def _update_anonymized_node(self, node: GraphNode, old_id: str) -> None:
|
|
1330
|
+
"""Update anonymized node in memory bus."""
|
|
1331
|
+
if not self._memory_bus:
|
|
1332
|
+
return
|
|
1333
|
+
|
|
1334
|
+
try:
|
|
1335
|
+
status = await self._memory_bus.memorize(node, handler_name="tsdb_consolidation")
|
|
1336
|
+
if status.status == MemoryOpStatus.SUCCESS:
|
|
1337
|
+
logger.info(f"Successfully anonymized node {old_id}")
|
|
1338
|
+
else:
|
|
1339
|
+
logger.warning(f"Failed to update anonymized node: {status.reason}")
|
|
1340
|
+
except Exception as e:
|
|
1341
|
+
logger.error(f"Error updating anonymized node: {e}")
|
|
1342
|
+
|
|
1343
|
+
async def _handle_consent_expiry(self, nodes_by_type: Dict[str, TSDBNodeQueryResult], period_end: datetime) -> None:
|
|
1344
|
+
"""
|
|
1345
|
+
Handle consent expiry by anonymizing expired TEMPORARY nodes.
|
|
1346
|
+
|
|
1347
|
+
When a TEMPORARY node expires (14 days), it gets renamed from:
|
|
1348
|
+
- user_<id> -> temporary_user_<hash>
|
|
1349
|
+
|
|
1350
|
+
When transitioning to ANONYMOUS, it becomes:
|
|
1351
|
+
- user_<id> -> anonymous_user_<hash>
|
|
1352
|
+
|
|
1353
|
+
Args:
|
|
1354
|
+
nodes_by_type: All nodes in the period by type
|
|
1355
|
+
period_end: End of the consolidation period
|
|
1356
|
+
"""
|
|
1357
|
+
# Check user nodes for expiry
|
|
1358
|
+
user_nodes = nodes_by_type.get(
|
|
1359
|
+
"user", TSDBNodeQueryResult(nodes=[], period_start=period_end, period_end=period_end)
|
|
1360
|
+
).nodes
|
|
1361
|
+
|
|
1362
|
+
for node in user_nodes:
|
|
1363
|
+
if not self._should_anonymize_node(node, period_end):
|
|
1364
|
+
continue
|
|
1365
|
+
|
|
1366
|
+
# Node has expired - anonymize it
|
|
1367
|
+
old_id = node.id
|
|
1368
|
+
new_id = self._generate_anonymized_id(old_id, node.consent_stream)
|
|
1369
|
+
|
|
1370
|
+
logger.info(f"Anonymizing expired node: {old_id} -> {new_id}")
|
|
1371
|
+
|
|
1372
|
+
# Update the node ID
|
|
1373
|
+
node.id = new_id
|
|
1374
|
+
|
|
1375
|
+
# Clear any PII from attributes
|
|
1376
|
+
self._remove_pii_from_attributes(node, period_end)
|
|
1377
|
+
|
|
1378
|
+
# Update in memory bus
|
|
1379
|
+
await self._update_anonymized_node(node, old_id)
|
|
1380
|
+
|
|
1381
|
+
def _get_actions(self) -> List[str]:
|
|
1382
|
+
"""Get list of actions this service can handle."""
|
|
1383
|
+
# Graph services typically don't handle actions through buses
|
|
1384
|
+
return []
|
|
1385
|
+
|
|
1386
|
+
async def _run_extensive_consolidation(self) -> None:
|
|
1387
|
+
"""
|
|
1388
|
+
Run extensive consolidation - consolidates basic summaries from the past week.
|
|
1389
|
+
This reduces data volume by creating daily summaries (4 basic summaries → 1 daily summary).
|
|
1390
|
+
Creates 7 daily summaries for each node type.
|
|
1391
|
+
"""
|
|
1392
|
+
from ciris_engine.logic.persistence.db.core import get_db_connection
|
|
1393
|
+
from ciris_engine.logic.services.graph.tsdb_consolidation.aggregation_helpers import (
|
|
1394
|
+
aggregate_action_counts,
|
|
1395
|
+
aggregate_metric_stats,
|
|
1396
|
+
aggregate_resource_usage,
|
|
1397
|
+
group_summaries_by_day,
|
|
1398
|
+
parse_summary_attributes,
|
|
1399
|
+
)
|
|
1400
|
+
from ciris_engine.logic.services.graph.tsdb_consolidation.date_calculation_helpers import calculate_week_period
|
|
1401
|
+
from ciris_engine.logic.services.graph.tsdb_consolidation.extensive_helpers import (
|
|
1402
|
+
check_daily_summary_exists,
|
|
1403
|
+
create_daily_summary_attributes,
|
|
1404
|
+
create_daily_summary_node,
|
|
1405
|
+
maintain_temporal_chain_to_daily,
|
|
1406
|
+
query_basic_summaries_in_period,
|
|
1407
|
+
)
|
|
1408
|
+
from ciris_engine.schemas.services.operations import MemoryOpStatus
|
|
1409
|
+
|
|
1410
|
+
consolidation_start = self._now()
|
|
1411
|
+
total_basic_summaries = 0
|
|
1412
|
+
daily_summaries_created = 0
|
|
1413
|
+
|
|
1414
|
+
try:
|
|
1415
|
+
logger.info("=" * 60)
|
|
1416
|
+
logger.info("Starting extensive (weekly) consolidation")
|
|
1417
|
+
logger.info(f"Started at: {consolidation_start.isoformat()}")
|
|
1418
|
+
|
|
1419
|
+
now = self._now()
|
|
1420
|
+
|
|
1421
|
+
# Calculate the previous week period using helper
|
|
1422
|
+
period_start, period_end = calculate_week_period(now)
|
|
1423
|
+
|
|
1424
|
+
week_start = period_start.date()
|
|
1425
|
+
week_end = period_end.date()
|
|
1426
|
+
|
|
1427
|
+
logger.info(f"Consolidating week: {week_start} to {week_end}")
|
|
1428
|
+
logger.info(f"Period: {period_start.isoformat()} to {period_end.isoformat()}")
|
|
1429
|
+
|
|
1430
|
+
# Try to acquire lock for this week to prevent duplicate consolidation
|
|
1431
|
+
week_identifier = week_start.isoformat() # e.g., "2023-10-01"
|
|
1432
|
+
lock_acquired = self._query_manager.acquire_consolidation_lock("extensive", week_identifier)
|
|
1433
|
+
|
|
1434
|
+
if not lock_acquired:
|
|
1435
|
+
logger.info(
|
|
1436
|
+
f"Extensive consolidation for week {week_identifier} is locked by another instance, skipping"
|
|
1437
|
+
)
|
|
1438
|
+
return
|
|
1439
|
+
|
|
1440
|
+
try:
|
|
1441
|
+
# Process summaries
|
|
1442
|
+
with get_db_connection(db_path=self.db_path) as conn:
|
|
1443
|
+
cursor = conn.cursor()
|
|
1444
|
+
|
|
1445
|
+
# Get all summary types to consolidate
|
|
1446
|
+
summary_types = [
|
|
1447
|
+
"tsdb_summary",
|
|
1448
|
+
"audit_summary",
|
|
1449
|
+
"trace_summary",
|
|
1450
|
+
"conversation_summary",
|
|
1451
|
+
"task_summary",
|
|
1452
|
+
]
|
|
1453
|
+
|
|
1454
|
+
for summary_type in summary_types:
|
|
1455
|
+
# Query basic summaries using helper
|
|
1456
|
+
summaries = query_basic_summaries_in_period(cursor, summary_type, period_start, period_end)
|
|
1457
|
+
|
|
1458
|
+
if not summaries:
|
|
1459
|
+
logger.info(f"No {summary_type} summaries found for consolidation")
|
|
1460
|
+
continue
|
|
1461
|
+
|
|
1462
|
+
logger.info(f"Found {len(summaries)} {summary_type} summaries to consolidate")
|
|
1463
|
+
total_basic_summaries += len(summaries)
|
|
1464
|
+
|
|
1465
|
+
# Group summaries by day using helper
|
|
1466
|
+
summaries_by_day = group_summaries_by_day(summaries)
|
|
1467
|
+
|
|
1468
|
+
# Create daily summary for each day
|
|
1469
|
+
for day, day_summaries in summaries_by_day.items():
|
|
1470
|
+
if len(day_summaries) == 0:
|
|
1471
|
+
continue
|
|
1472
|
+
|
|
1473
|
+
# Convert date to datetime for helpers
|
|
1474
|
+
day_datetime = datetime.combine(day, datetime.min.time(), tzinfo=timezone.utc)
|
|
1475
|
+
|
|
1476
|
+
# Generate daily node ID
|
|
1477
|
+
daily_node_id = f"{summary_type}_daily_{day.strftime('%Y%m%d')}"
|
|
1478
|
+
|
|
1479
|
+
# Check if already exists using helper
|
|
1480
|
+
if check_daily_summary_exists(cursor, daily_node_id):
|
|
1481
|
+
logger.debug(f"Daily summary {daily_node_id} already exists, skipping")
|
|
1482
|
+
continue
|
|
1483
|
+
|
|
1484
|
+
# Parse summary attributes using helper
|
|
1485
|
+
summary_attrs_list = parse_summary_attributes(day_summaries)
|
|
1486
|
+
|
|
1487
|
+
# Aggregate metrics, resources, and actions using helpers
|
|
1488
|
+
daily_metrics = aggregate_metric_stats(summary_attrs_list)
|
|
1489
|
+
daily_resources = aggregate_resource_usage(summary_attrs_list)
|
|
1490
|
+
daily_action_counts = aggregate_action_counts(summary_attrs_list)
|
|
1491
|
+
|
|
1492
|
+
# Create daily summary attributes using helper
|
|
1493
|
+
daily_attrs = create_daily_summary_attributes(
|
|
1494
|
+
summary_type,
|
|
1495
|
+
day_datetime,
|
|
1496
|
+
day_summaries,
|
|
1497
|
+
daily_metrics,
|
|
1498
|
+
daily_resources,
|
|
1499
|
+
daily_action_counts,
|
|
1500
|
+
)
|
|
1501
|
+
|
|
1502
|
+
# Create daily summary node using helper
|
|
1503
|
+
daily_summary = create_daily_summary_node(summary_type, day_datetime, daily_attrs, now)
|
|
1504
|
+
|
|
1505
|
+
# Store in memory
|
|
1506
|
+
if self._memory_bus:
|
|
1507
|
+
result = await self._memory_bus.memorize(
|
|
1508
|
+
daily_summary, handler_name="tsdb_consolidation"
|
|
1509
|
+
)
|
|
1510
|
+
if result.status == MemoryOpStatus.OK:
|
|
1511
|
+
daily_summaries_created += 1
|
|
1512
|
+
logger.info(
|
|
1513
|
+
f"Created daily summary {daily_node_id} from {len(day_summaries)} basic summaries"
|
|
1514
|
+
)
|
|
1515
|
+
|
|
1516
|
+
# Final summary
|
|
1517
|
+
total_duration = (self._now() - consolidation_start).total_seconds()
|
|
1518
|
+
logger.info(f"Extensive consolidation complete in {total_duration:.2f}s:")
|
|
1519
|
+
logger.info(f" - Basic summaries processed: {total_basic_summaries}")
|
|
1520
|
+
logger.info(f" - Daily summaries created: {daily_summaries_created}")
|
|
1521
|
+
if total_basic_summaries > 0:
|
|
1522
|
+
compression_ratio = total_basic_summaries / max(daily_summaries_created, 1)
|
|
1523
|
+
logger.info(f" - Compression ratio: {compression_ratio:.1f}:1")
|
|
1524
|
+
logger.info("=" * 60)
|
|
1525
|
+
|
|
1526
|
+
# Maintain temporal chain using helper
|
|
1527
|
+
if daily_summaries_created > 0:
|
|
1528
|
+
edges_created = maintain_temporal_chain_to_daily(cursor, period_start)
|
|
1529
|
+
if edges_created > 0:
|
|
1530
|
+
logger.info(f"Created {edges_created} temporal chain edges")
|
|
1531
|
+
|
|
1532
|
+
finally:
|
|
1533
|
+
# Release lock
|
|
1534
|
+
self._query_manager.release_consolidation_lock("extensive", week_identifier)
|
|
1535
|
+
|
|
1536
|
+
except Exception as e:
|
|
1537
|
+
logger.error(f"Extensive consolidation failed: {e}", exc_info=True)
|
|
1538
|
+
|
|
1539
|
+
async def get_metrics(self) -> Dict[str, float]:
|
|
1540
|
+
"""Get TSDB consolidation service metrics.
|
|
1541
|
+
|
|
1542
|
+
Returns exactly the 4 metrics from v1.4.3 API specification:
|
|
1543
|
+
- tsdb_consolidations_total: Total consolidations performed
|
|
1544
|
+
- tsdb_datapoints_processed: Total data points processed
|
|
1545
|
+
- tsdb_storage_saved_mb: Storage saved by consolidation (MB)
|
|
1546
|
+
- tsdb_uptime_seconds: Service uptime in seconds
|
|
1547
|
+
"""
|
|
1548
|
+
# Calculate uptime
|
|
1549
|
+
uptime_seconds = 0.0
|
|
1550
|
+
if hasattr(self, "_start_time") and self._start_time:
|
|
1551
|
+
uptime_seconds = (self._now() - self._start_time).total_seconds()
|
|
1552
|
+
|
|
1553
|
+
# Calculate total consolidations performed
|
|
1554
|
+
total_consolidations = (
|
|
1555
|
+
self._basic_consolidations + self._extensive_consolidations + self._profound_consolidations
|
|
1556
|
+
)
|
|
1557
|
+
|
|
1558
|
+
# Calculate storage saved (estimate based on compression ratio and records processed)
|
|
1559
|
+
# Each record averages ~2KB, storage saved = records_deleted * avg_size_kb / 1024
|
|
1560
|
+
avg_record_size_kb = 2.0
|
|
1561
|
+
storage_saved_mb = (self._records_deleted * avg_record_size_kb) / 1024.0
|
|
1562
|
+
|
|
1563
|
+
return {
|
|
1564
|
+
"tsdb_consolidations_total": float(total_consolidations),
|
|
1565
|
+
"tsdb_datapoints_processed": float(self._records_consolidated),
|
|
1566
|
+
"tsdb_storage_saved_mb": storage_saved_mb,
|
|
1567
|
+
"tsdb_uptime_seconds": uptime_seconds,
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
def _run_profound_consolidation(self) -> None:
|
|
1571
|
+
"""
|
|
1572
|
+
Run profound consolidation - compresses existing daily summaries in-place.
|
|
1573
|
+
Target: Configurable MB per day of data retention.
|
|
1574
|
+
|
|
1575
|
+
This process compresses daily summaries to meet storage targets without
|
|
1576
|
+
creating new nodes. Future versions will handle multimedia compression.
|
|
1577
|
+
"""
|
|
1578
|
+
from ciris_engine.logic.persistence.db.core import get_db_connection
|
|
1579
|
+
from ciris_engine.logic.services.graph.tsdb_consolidation.date_calculation_helpers import calculate_month_period
|
|
1580
|
+
from ciris_engine.logic.services.graph.tsdb_consolidation.profound_helpers import (
|
|
1581
|
+
calculate_storage_metrics,
|
|
1582
|
+
cleanup_old_basic_summaries,
|
|
1583
|
+
compress_and_update_summaries,
|
|
1584
|
+
query_extensive_summaries_in_month,
|
|
1585
|
+
)
|
|
1586
|
+
|
|
1587
|
+
from .compressor import SummaryCompressor
|
|
1588
|
+
|
|
1589
|
+
consolidation_start = self._now()
|
|
1590
|
+
total_daily_summaries = 0
|
|
1591
|
+
summaries_compressed = 0
|
|
1592
|
+
storage_before_mb = 0.0
|
|
1593
|
+
storage_after_mb = 0.0
|
|
1594
|
+
|
|
1595
|
+
try:
|
|
1596
|
+
logger.info("=" * 60)
|
|
1597
|
+
logger.info("Starting profound (monthly) consolidation")
|
|
1598
|
+
logger.info(f"Started at: {consolidation_start.isoformat()}")
|
|
1599
|
+
|
|
1600
|
+
now = self._now()
|
|
1601
|
+
|
|
1602
|
+
# Calculate the previous month period using helper
|
|
1603
|
+
month_start, month_end = calculate_month_period(now)
|
|
1604
|
+
|
|
1605
|
+
# Try to acquire lock for this month to prevent duplicate consolidation
|
|
1606
|
+
month_identifier = month_start.strftime("%Y-%m") # e.g., "2023-10"
|
|
1607
|
+
lock_acquired = self._query_manager.acquire_consolidation_lock("profound", month_identifier)
|
|
1608
|
+
|
|
1609
|
+
if not lock_acquired:
|
|
1610
|
+
logger.info(
|
|
1611
|
+
f"Profound consolidation for month {month_identifier} is locked by another instance, skipping"
|
|
1612
|
+
)
|
|
1613
|
+
return
|
|
1614
|
+
|
|
1615
|
+
try:
|
|
1616
|
+
# Initialize compressor
|
|
1617
|
+
compressor = SummaryCompressor(self._profound_target_mb_per_day)
|
|
1618
|
+
|
|
1619
|
+
# Query and process summaries
|
|
1620
|
+
with get_db_connection(db_path=self.db_path) as conn:
|
|
1621
|
+
cursor = conn.cursor()
|
|
1622
|
+
|
|
1623
|
+
# Query all extensive summaries from the month using helper
|
|
1624
|
+
summaries = query_extensive_summaries_in_month(cursor, month_start, month_end)
|
|
1625
|
+
total_daily_summaries = len(summaries)
|
|
1626
|
+
|
|
1627
|
+
if len(summaries) < 7: # Less than a week's worth
|
|
1628
|
+
logger.info(
|
|
1629
|
+
f"Not enough daily summaries for profound consolidation (found {len(summaries)}, need at least 7)"
|
|
1630
|
+
)
|
|
1631
|
+
return
|
|
1632
|
+
|
|
1633
|
+
logger.info(f"Found {total_daily_summaries} daily summaries to compress")
|
|
1634
|
+
|
|
1635
|
+
# Calculate current storage using helper
|
|
1636
|
+
days_in_period = (month_end - month_start).days + 1
|
|
1637
|
+
current_daily_mb, summary_attrs_list = calculate_storage_metrics(
|
|
1638
|
+
cursor, month_start, month_end, compressor
|
|
1639
|
+
)
|
|
1640
|
+
storage_before_mb = float(current_daily_mb * days_in_period)
|
|
1641
|
+
logger.info(f"Current storage: {current_daily_mb:.2f}MB/day ({storage_before_mb:.2f}MB total)")
|
|
1642
|
+
logger.info(f"Target: {self._profound_target_mb_per_day}MB/day")
|
|
1643
|
+
|
|
1644
|
+
# Check if compression is needed
|
|
1645
|
+
if not compressor.needs_compression(summary_attrs_list, days_in_period):
|
|
1646
|
+
logger.info("Daily summaries already meet storage target, skipping compression")
|
|
1647
|
+
return
|
|
1648
|
+
|
|
1649
|
+
# Compress summaries using helper
|
|
1650
|
+
compressed_count, total_reduction = compress_and_update_summaries(
|
|
1651
|
+
cursor, summaries, compressor, now
|
|
1652
|
+
)
|
|
1653
|
+
summaries_compressed = compressed_count
|
|
1654
|
+
|
|
1655
|
+
conn.commit()
|
|
1656
|
+
|
|
1657
|
+
# Calculate new storage using helper
|
|
1658
|
+
new_daily_mb, _ = calculate_storage_metrics(cursor, month_start, month_end, compressor)
|
|
1659
|
+
storage_after_mb = new_daily_mb * days_in_period
|
|
1660
|
+
avg_reduction = total_reduction / compressed_count if compressed_count > 0 else 0
|
|
1661
|
+
|
|
1662
|
+
# Final summary
|
|
1663
|
+
total_duration = (self._now() - consolidation_start).total_seconds()
|
|
1664
|
+
logger.info(f"Profound consolidation complete in {total_duration:.2f}s:")
|
|
1665
|
+
logger.info(f" - Daily summaries processed: {total_daily_summaries}")
|
|
1666
|
+
logger.info(f" - Summaries compressed: {summaries_compressed}")
|
|
1667
|
+
logger.info(f" - Average compression: {avg_reduction:.1%}")
|
|
1668
|
+
logger.info(
|
|
1669
|
+
f" - Storage before: {storage_before_mb:.2f}MB ({storage_before_mb/days_in_period:.2f}MB/day)"
|
|
1670
|
+
)
|
|
1671
|
+
logger.info(f" - Storage after: {storage_after_mb:.2f}MB ({new_daily_mb:.2f}MB/day)")
|
|
1672
|
+
if storage_before_mb > 0:
|
|
1673
|
+
logger.info(
|
|
1674
|
+
f" - Total reduction: {((storage_before_mb - storage_after_mb) / storage_before_mb * 100):.1f}%"
|
|
1675
|
+
)
|
|
1676
|
+
|
|
1677
|
+
# Clean up old basic summaries using helper
|
|
1678
|
+
cleanup_cutoff = now - timedelta(days=30)
|
|
1679
|
+
deleted = cleanup_old_basic_summaries(cursor, cleanup_cutoff)
|
|
1680
|
+
|
|
1681
|
+
if deleted > 0:
|
|
1682
|
+
logger.info(f"Cleaned up {deleted} old basic summaries")
|
|
1683
|
+
conn.commit()
|
|
1684
|
+
|
|
1685
|
+
self._last_profound_consolidation = now
|
|
1686
|
+
|
|
1687
|
+
finally:
|
|
1688
|
+
# Release lock
|
|
1689
|
+
self._query_manager.release_consolidation_lock("profound", month_identifier)
|
|
1690
|
+
|
|
1691
|
+
except Exception as e:
|
|
1692
|
+
logger.error(f"Profound consolidation failed: {e}", exc_info=True)
|