contextsynapse 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (776) hide show
  1. contextsynapse/__init__.py +107 -0
  2. contextsynapse/__main__.py +9 -0
  3. contextsynapse/a2a/__init__.py +28 -0
  4. contextsynapse/a2a/client.py +120 -0
  5. contextsynapse/a2a/discovery.py +128 -0
  6. contextsynapse/a2a/handlers.py +164 -0
  7. contextsynapse/a2a/models.py +246 -0
  8. contextsynapse/a2a/streaming.py +92 -0
  9. contextsynapse/a2a/task_manager.py +275 -0
  10. contextsynapse/adapters/__init__.py +98 -0
  11. contextsynapse/adapters/_base.py +298 -0
  12. contextsynapse/adapters/autogen/__init__.py +4 -0
  13. contextsynapse/adapters/autogen/tools.py +34 -0
  14. contextsynapse/adapters/crewai/__init__.py +4 -0
  15. contextsynapse/adapters/crewai/tools.py +63 -0
  16. contextsynapse/adapters/langchain/__init__.py +19 -0
  17. contextsynapse/adapters/langchain/message_history.py +17 -0
  18. contextsynapse/adapters/langchain/retriever.py +158 -0
  19. contextsynapse/adapters/langchain/tools.py +43 -0
  20. contextsynapse/adapters/langgraph/__init__.py +21 -0
  21. contextsynapse/adapters/langgraph/checkpoint.py +223 -0
  22. contextsynapse/adapters/langgraph/context_tools.py +341 -0
  23. contextsynapse/adapters/langgraph/message_history.py +138 -0
  24. contextsynapse/adapters/langgraph/tools.py +43 -0
  25. contextsynapse/adapters/llamaindex/__init__.py +4 -0
  26. contextsynapse/adapters/llamaindex/tools.py +64 -0
  27. contextsynapse/adapters/openai/__init__.py +24 -0
  28. contextsynapse/adapters/openai/tools.py +206 -0
  29. contextsynapse/adapters/pydantic_ai/__init__.py +4 -0
  30. contextsynapse/adapters/pydantic_ai/tools.py +36 -0
  31. contextsynapse/adapters/swarm/__init__.py +4 -0
  32. contextsynapse/adapters/swarm/tools.py +44 -0
  33. contextsynapse/agents/__init__.py +6 -0
  34. contextsynapse/agents/auto_capture.py +80 -0
  35. contextsynapse/agents/dispatcher.py +299 -0
  36. contextsynapse/agents/notifications.py +112 -0
  37. contextsynapse/aiql/__init__.py +71 -0
  38. contextsynapse/aiql/cache.py +341 -0
  39. contextsynapse/aiql/compiler/__init__.py +62 -0
  40. contextsynapse/aiql/compiler/optimizer.py +126 -0
  41. contextsynapse/aiql/compiler/planner.py +366 -0
  42. contextsynapse/aiql/compiler/statistics.py +235 -0
  43. contextsynapse/aiql/compiler/validator.py +425 -0
  44. contextsynapse/aiql/engine/__init__.py +11 -0
  45. contextsynapse/aiql/engine/chunk_storage.py +2584 -0
  46. contextsynapse/aiql/engine/executor.py +7737 -0
  47. contextsynapse/aiql/engine/extraction_integration.py +442 -0
  48. contextsynapse/aiql/engine/graph_builder.py +405 -0
  49. contextsynapse/aiql/engine/node_creation_from_normalized.py +691 -0
  50. contextsynapse/aiql/engine/performance_monitor.py +289 -0
  51. contextsynapse/aiql/engine/postprocessing.py +365 -0
  52. contextsynapse/aiql/engine/ray_stage_executors.py +918 -0
  53. contextsynapse/aiql/engine/redis_cache.py +106 -0
  54. contextsynapse/aiql/engine/streaming_json.py +366 -0
  55. contextsynapse/aiql/engine/streaming_parquet.py +568 -0
  56. contextsynapse/aiql/engine/streaming_ray_chunking.py +419 -0
  57. contextsynapse/aiql/grammar/__init__.py +14 -0
  58. contextsynapse/aiql/grammar/aiql_grammar.py +1822 -0
  59. contextsynapse/aiql/grammar/base.py +71 -0
  60. contextsynapse/aiql/parser/__init__.py +46 -0
  61. contextsynapse/aiql/parser/aiql_parser.py +11330 -0
  62. contextsynapse/aiql/parser/base_parser.py +47 -0
  63. contextsynapse/api/__init__.py +5 -0
  64. contextsynapse/api/a2a_router.py +233 -0
  65. contextsynapse/api/admin_router.py +140 -0
  66. contextsynapse/api/agent_worker_router.py +978 -0
  67. contextsynapse/api/algorithms_router.py +317 -0
  68. contextsynapse/api/api.py +4144 -0
  69. contextsynapse/api/audit.py +204 -0
  70. contextsynapse/api/auth.py +441 -0
  71. contextsynapse/api/auth_router.py +537 -0
  72. contextsynapse/api/billing.py +261 -0
  73. contextsynapse/api/billing_router.py +275 -0
  74. contextsynapse/api/boundary_router.py +452 -0
  75. contextsynapse/api/cognition_router.py +116 -0
  76. contextsynapse/api/connector_router.py +237 -0
  77. contextsynapse/api/context_router.py +1619 -0
  78. contextsynapse/api/dashboard_router.py +11367 -0
  79. contextsynapse/api/errors.py +183 -0
  80. contextsynapse/api/events.py +88 -0
  81. contextsynapse/api/experiment_bootstrap.py +321 -0
  82. contextsynapse/api/experiment_orchestrator.py +1078 -0
  83. contextsynapse/api/experiment_router.py +2089 -0
  84. contextsynapse/api/feature_gate.py +89 -0
  85. contextsynapse/api/federation_router.py +108 -0
  86. contextsynapse/api/integrations.py +567 -0
  87. contextsynapse/api/integrations_router.py +461 -0
  88. contextsynapse/api/intelligence_router.py +1028 -0
  89. contextsynapse/api/metering.py +155 -0
  90. contextsynapse/api/metrics.py +112 -0
  91. contextsynapse/api/models.py +38 -0
  92. contextsynapse/api/monitoring_router.py +174 -0
  93. contextsynapse/api/pipeline_router.py +1437 -0
  94. contextsynapse/api/playground_conn.py +116 -0
  95. contextsynapse/api/projects_router.py +131 -0
  96. contextsynapse/api/queries_router.py +282 -0
  97. contextsynapse/api/quota.py +95 -0
  98. contextsynapse/api/rate_limit.py +64 -0
  99. contextsynapse/api/search_router.py +261 -0
  100. contextsynapse/api/shield_router.py +447 -0
  101. contextsynapse/api/tenant_guard.py +103 -0
  102. contextsynapse/api/tenant_quotas.py +231 -0
  103. contextsynapse/api/tenants.py +305 -0
  104. contextsynapse/api/users.py +646 -0
  105. contextsynapse/api/v1.py +167 -0
  106. contextsynapse/api/vertical_builder_router.py +387 -0
  107. contextsynapse/benchmark/__init__.py +15 -0
  108. contextsynapse/benchmark/__main__.py +64 -0
  109. contextsynapse/benchmark/agents.py +338 -0
  110. contextsynapse/benchmark/live_ab.py +338 -0
  111. contextsynapse/benchmark/reporter.py +149 -0
  112. contextsynapse/benchmark/runner.py +264 -0
  113. contextsynapse/benchmark/savings.py +266 -0
  114. contextsynapse/benchmark/token_counter.py +212 -0
  115. contextsynapse/benchmark/workloads.py +289 -0
  116. contextsynapse/buffer/__init__.py +154 -0
  117. contextsynapse/buffer/base.py +257 -0
  118. contextsynapse/buffer/duckdb_buffer.py +246 -0
  119. contextsynapse/buffer/file_buffer.py +229 -0
  120. contextsynapse/buffer/lmdb_buffer.py +286 -0
  121. contextsynapse/buffer/local_buffer.py +154 -0
  122. contextsynapse/buffer/redis_buffer.py +211 -0
  123. contextsynapse/buffer/sqlite_buffer.py +266 -0
  124. contextsynapse/cache.py +2 -0
  125. contextsynapse/cli/__init__.py +40 -0
  126. contextsynapse/cli/cli.py +475 -0
  127. contextsynapse/cli/config_cli.py +177 -0
  128. contextsynapse/cli/scaffold.py +465 -0
  129. contextsynapse/cognition/__init__.py +31 -0
  130. contextsynapse/cognition/derivation.py +55 -0
  131. contextsynapse/cognition/events.py +135 -0
  132. contextsynapse/cognition/feedback.py +52 -0
  133. contextsynapse/cognition/hallucination.py +90 -0
  134. contextsynapse/cognition/invalidation.py +70 -0
  135. contextsynapse/cognition/read_tracker.py +50 -0
  136. contextsynapse/collaboration.py +2 -0
  137. contextsynapse/comparison.py +2 -0
  138. contextsynapse/config/__init__.py +10 -0
  139. contextsynapse/config/connectors/amfi_nav.yaml +18 -0
  140. contextsynapse/config/connectors/auto_sector.yaml +20 -0
  141. contextsynapse/config/connectors/banking_sector.yaml +20 -0
  142. contextsynapse/config/connectors/energy_sector.yaml +20 -0
  143. contextsynapse/config/connectors/fmcg_sector.yaml +20 -0
  144. contextsynapse/config/connectors/nsdl_fpi.yaml +9 -0
  145. contextsynapse/config/connectors/nse_price.yaml +61 -0
  146. contextsynapse/config/connectors/pharma_sector.yaml +20 -0
  147. contextsynapse/config/connectors/rbi_dbie.yaml +12 -0
  148. contextsynapse/config/context_presets/ai_research.json +25 -0
  149. contextsynapse/config/context_presets/chatgpt_conversations.json +19 -0
  150. contextsynapse/config/context_presets/claude_conversations.json +19 -0
  151. contextsynapse/config/context_presets/legal_compliance.json +21 -0
  152. contextsynapse/config/context_presets/meeting_notes.json +21 -0
  153. contextsynapse/config/context_presets/slack_engineering.json +21 -0
  154. contextsynapse/config/context_presets/tech_climate.json +24 -0
  155. contextsynapse/config/context_presets/toi_politics.json +25 -0
  156. contextsynapse/config/database_config.py +453 -0
  157. contextsynapse/config/deployment.py +221 -0
  158. contextsynapse/config/gpt_action_schema.json +164 -0
  159. contextsynapse/config/indicators/india_macro.yaml +213 -0
  160. contextsynapse/config/logging_config.py +88 -0
  161. contextsynapse/config/mode_config.py +223 -0
  162. contextsynapse/config/schemas/api_spec.yaml +48 -0
  163. contextsynapse/config/schemas/architecture_doc.yaml +51 -0
  164. contextsynapse/config/schemas/chat_history.yaml +90 -0
  165. contextsynapse/config/schemas/chatgpt_conversation.yaml +101 -0
  166. contextsynapse/config/schemas/claude_conversation.yaml +148 -0
  167. contextsynapse/config/schemas/contract.yaml +48 -0
  168. contextsynapse/config/schemas/conversation.yaml +167 -0
  169. contextsynapse/config/schemas/database.yaml +74 -0
  170. contextsynapse/config/schemas/decision.yaml +72 -0
  171. contextsynapse/config/schemas/ecommerce.yaml +56 -0
  172. contextsynapse/config/schemas/education.yaml +42 -0
  173. contextsynapse/config/schemas/healthcare.yaml +133 -0
  174. contextsynapse/config/schemas/hr_document.yaml +48 -0
  175. contextsynapse/config/schemas/invoice.yaml +40 -0
  176. contextsynapse/config/schemas/knowledge_base.yaml +75 -0
  177. contextsynapse/config/schemas/legal.yaml +42 -0
  178. contextsynapse/config/schemas/marketing_content.yaml +48 -0
  179. contextsynapse/config/schemas/meeting_notes.yaml +51 -0
  180. contextsynapse/config/schemas/news_article.yaml +61 -0
  181. contextsynapse/config/schemas/real_estate.yaml +49 -0
  182. contextsynapse/config/schemas/requirements_doc.yaml +48 -0
  183. contextsynapse/config/schemas/research_paper.yaml +56 -0
  184. contextsynapse/config/schemas/resume.yaml +49 -0
  185. contextsynapse/config/schemas/retail.yaml +42 -0
  186. contextsynapse/config/schemas/rule_meta.yaml +173 -0
  187. contextsynapse/config/schemas/rules.yaml +72 -0
  188. contextsynapse/config/schemas/screener_meta.yaml +236 -0
  189. contextsynapse/config/schemas/sdlc.yaml +103 -0
  190. contextsynapse/config/schemas/session_graph.yaml +172 -0
  191. contextsynapse/config/schemas/support_ticket.yaml +48 -0
  192. contextsynapse/config/schemas/web.yaml +64 -0
  193. contextsynapse/config/sensors/earnings_calendar.yaml +52 -0
  194. contextsynapse/config/sensors/global_risk.yaml +53 -0
  195. contextsynapse/config/sensors/government_policy.yaml +54 -0
  196. contextsynapse/config/sensors/supply_chain.yaml +50 -0
  197. contextsynapse/config/sensors/templates/behavioral.yaml +56 -0
  198. contextsynapse/config/sensors/templates/equity_sentiment.yaml +48 -0
  199. contextsynapse/config/sensors/templates/global_macro.yaml +47 -0
  200. contextsynapse/config/sensors/templates/market_data.yaml +118 -0
  201. contextsynapse/config/sensors/templates/sector_technical.yaml +47 -0
  202. contextsynapse/config/sensors/weather.yaml +52 -0
  203. contextsynapse/config/storage_strategy.py +202 -0
  204. contextsynapse/config/templates/competitive_intel.yaml +39 -0
  205. contextsynapse/config/templates/geopolitical_risk.yaml +46 -0
  206. contextsynapse/config/templates/market_analysis.yaml +58 -0
  207. contextsynapse/connectors/__init__.py +15 -0
  208. contextsynapse/connectors/api_router.py +121 -0
  209. contextsynapse/connectors/base.py +357 -0
  210. contextsynapse/connectors/financial/audio.py +170 -0
  211. contextsynapse/connectors/financial/youtube.py +272 -0
  212. contextsynapse/connectors/github.py +164 -0
  213. contextsynapse/connectors/jira.py +120 -0
  214. contextsynapse/connectors/kafka_connector.py +124 -0
  215. contextsynapse/connectors/pipeline_connector.py +747 -0
  216. contextsynapse/connectors/registry.py +116 -0
  217. contextsynapse/connectors/scheduler.py +87 -0
  218. contextsynapse/connectors/universal.py +360 -0
  219. contextsynapse/context/__init__.py +96 -0
  220. contextsynapse/context/__main__.py +4 -0
  221. contextsynapse/context/acl.py +125 -0
  222. contextsynapse/context/agent_card.py +201 -0
  223. contextsynapse/context/agent_memory.py +814 -0
  224. contextsynapse/context/agents.py +472 -0
  225. contextsynapse/context/agents_redis.py +391 -0
  226. contextsynapse/context/assembled.py +204 -0
  227. contextsynapse/context/attribution.py +113 -0
  228. contextsynapse/context/blob.py +184 -0
  229. contextsynapse/context/boundaries.py +187 -0
  230. contextsynapse/context/boundary.py +507 -0
  231. contextsynapse/context/bundle.py +155 -0
  232. contextsynapse/context/cli.py +519 -0
  233. contextsynapse/context/collaboration.py +165 -0
  234. contextsynapse/context/compiler.py +604 -0
  235. contextsynapse/context/composite.py +536 -0
  236. contextsynapse/context/compression.py +147 -0
  237. contextsynapse/context/context_manager.py +888 -0
  238. contextsynapse/context/context_schema.py +90 -0
  239. contextsynapse/context/context_schema.yaml +105 -0
  240. contextsynapse/context/context_units.py +891 -0
  241. contextsynapse/context/conversation.py +488 -0
  242. contextsynapse/context/dedup.py +207 -0
  243. contextsynapse/context/document_processor.py +698 -0
  244. contextsynapse/context/embedding_hooks.py +338 -0
  245. contextsynapse/context/execution_schema.py +274 -0
  246. contextsynapse/context/fan_out.py +138 -0
  247. contextsynapse/context/frozen.py +80 -0
  248. contextsynapse/context/gravity.py +227 -0
  249. contextsynapse/context/hub.py +1401 -0
  250. contextsynapse/context/ingest.py +391 -0
  251. contextsynapse/context/injection.py +226 -0
  252. contextsynapse/context/interaction_graphifier.py +562 -0
  253. contextsynapse/context/layers.py +574 -0
  254. contextsynapse/context/projection.py +1195 -0
  255. contextsynapse/context/promotion.py +634 -0
  256. contextsynapse/context/proof.py +131 -0
  257. contextsynapse/context/propagation.py +577 -0
  258. contextsynapse/context/pubsub.py +135 -0
  259. contextsynapse/context/quality.py +1054 -0
  260. contextsynapse/context/quality_tracker.py +270 -0
  261. contextsynapse/context/scoping.py +370 -0
  262. contextsynapse/context/seed.py +992 -0
  263. contextsynapse/context/session.py +1142 -0
  264. contextsynapse/context/session_graph.py +593 -0
  265. contextsynapse/context/session_memory.py +309 -0
  266. contextsynapse/context/session_resolver.py +111 -0
  267. contextsynapse/context/skill_learning.py +237 -0
  268. contextsynapse/context/smart_budget.py +129 -0
  269. contextsynapse/context/source_policy.py +242 -0
  270. contextsynapse/context/store_factory.py +53 -0
  271. contextsynapse/context/sync.py +648 -0
  272. contextsynapse/context/token.py +236 -0
  273. contextsynapse/context/vector_integration.py +297 -0
  274. contextsynapse/context/webhooks.py +194 -0
  275. contextsynapse/context/working_memory.py +224 -0
  276. contextsynapse/core/__init__.py +18 -0
  277. contextsynapse/core/archiver.py +237 -0
  278. contextsynapse/core/change_stream.py +282 -0
  279. contextsynapse/core/checkpoint.py +310 -0
  280. contextsynapse/core/cloud_storage.py +387 -0
  281. contextsynapse/core/context_state.py +651 -0
  282. contextsynapse/core/cron.py +324 -0
  283. contextsynapse/core/datasource.py +643 -0
  284. contextsynapse/core/db.py +289 -0
  285. contextsynapse/core/federation.py +283 -0
  286. contextsynapse/core/graph_coordinator.py +201 -0
  287. contextsynapse/core/graph_intelligence.py +388 -0
  288. contextsynapse/core/graph_structures.py +106 -0
  289. contextsynapse/core/graph_sync.py +157 -0
  290. contextsynapse/core/hybrid_graph_storage.py +2998 -0
  291. contextsynapse/core/lifecycle.py +691 -0
  292. contextsynapse/core/multiworker.py +378 -0
  293. contextsynapse/core/pruning.py +312 -0
  294. contextsynapse/core/redis_registry.py +524 -0
  295. contextsynapse/core/registry.py +1043 -0
  296. contextsynapse/core/registry_factory.py +51 -0
  297. contextsynapse/core/registry_metadata.py +294 -0
  298. contextsynapse/core/replay.py +354 -0
  299. contextsynapse/core/replication.py +339 -0
  300. contextsynapse/core/time_travel.py +260 -0
  301. contextsynapse/core/write_behind.py +152 -0
  302. contextsynapse/core/write_context.py +39 -0
  303. contextsynapse/db/__init__.py +1 -0
  304. contextsynapse/db/postgres.py +267 -0
  305. contextsynapse/db/profile.py +236 -0
  306. contextsynapse/db/rules.py +328 -0
  307. contextsynapse/demo/__init__.py +0 -0
  308. contextsynapse/demo/report.py +147 -0
  309. contextsynapse/demo/runner.py +520 -0
  310. contextsynapse/demo/scenario.py +165 -0
  311. contextsynapse/demo/session_demo.py +280 -0
  312. contextsynapse/demo/session_report.py +143 -0
  313. contextsynapse/demo/universal_runner.py +257 -0
  314. contextsynapse/engine/__init__.py +15 -0
  315. contextsynapse/engine/core.py +555 -0
  316. contextsynapse/extraction/__init__.py +69 -0
  317. contextsynapse/extraction/cli.py +274 -0
  318. contextsynapse/extraction/config.py +107 -0
  319. contextsynapse/extraction/examples/example_usage.py +118 -0
  320. contextsynapse/extraction/examples/sample_manifest.json +53 -0
  321. contextsynapse/extraction/examples/sample_normalized.json +185 -0
  322. contextsynapse/extraction/extract_engine.py +440 -0
  323. contextsynapse/extraction/extractors/__init__.py +37 -0
  324. contextsynapse/extraction/extractors/chat_export_extractor.py +189 -0
  325. contextsynapse/extraction/extractors/csv_extractor.py +83 -0
  326. contextsynapse/extraction/extractors/docx_extractor.py +129 -0
  327. contextsynapse/extraction/extractors/excel_extractor.py +152 -0
  328. contextsynapse/extraction/extractors/html_extractor.py +146 -0
  329. contextsynapse/extraction/extractors/pdf_extractor.py +635 -0
  330. contextsynapse/extraction/extractors/pdf_extractor_layoutparser.py +408 -0
  331. contextsynapse/extraction/extractors/register_extractors.py +63 -0
  332. contextsynapse/extraction/extractors/text_extractor.py +65 -0
  333. contextsynapse/extraction/extractors/txt_extractor.py +120 -0
  334. contextsynapse/extraction/extractors/website_extractor.py +246 -0
  335. contextsynapse/extraction/fact_extractor.py +236 -0
  336. contextsynapse/extraction/file_manager.py +353 -0
  337. contextsynapse/extraction/hierarchy.py +239 -0
  338. contextsynapse/extraction/id_generator.py +450 -0
  339. contextsynapse/extraction/layout_detector.py +409 -0
  340. contextsynapse/extraction/llm_entity_extractor.py +301 -0
  341. contextsynapse/extraction/normalized_store.py +480 -0
  342. contextsynapse/extraction/normalizer.py +594 -0
  343. contextsynapse/extraction/parquet_writer.py +598 -0
  344. contextsynapse/extraction/post_processors/__init__.py +40 -0
  345. contextsynapse/extraction/post_processors/column_reorganizer.py +507 -0
  346. contextsynapse/extraction/ray_normalization.py +233 -0
  347. contextsynapse/extraction/ray_runner.py +207 -0
  348. contextsynapse/extraction/redis_schema_registry.py +222 -0
  349. contextsynapse/extraction/registry.py +204 -0
  350. contextsynapse/extraction/schema_loader.py +663 -0
  351. contextsynapse/extraction/schema_registry.py +230 -0
  352. contextsynapse/extraction/semantic_normalizer.py +245 -0
  353. contextsynapse/extraction/version_manager.py +314 -0
  354. contextsynapse/gateway/__init__.py +24 -0
  355. contextsynapse/gateway/action_emitter.py +212 -0
  356. contextsynapse/gateway/cost_tracker.py +508 -0
  357. contextsynapse/gateway/gateway.py +377 -0
  358. contextsynapse/gateway/policy.py +303 -0
  359. contextsynapse/governance/__init__.py +13 -0
  360. contextsynapse/governance/layer.py +513 -0
  361. contextsynapse/ingestion/__init__.py +15 -0
  362. contextsynapse/ingestion/amplifier.py +288 -0
  363. contextsynapse/ingestion/async_ingest.py +463 -0
  364. contextsynapse/ingestion/chunker.py +303 -0
  365. contextsynapse/ingestion/chunking.py +58 -0
  366. contextsynapse/ingestion/cleaner.py +190 -0
  367. contextsynapse/ingestion/cleanse.py +252 -0
  368. contextsynapse/ingestion/connectors/__init__.py +38 -0
  369. contextsynapse/ingestion/connectors/base.py +300 -0
  370. contextsynapse/ingestion/connectors/chatgpt_memory.py +247 -0
  371. contextsynapse/ingestion/connectors/claude_memory.py +274 -0
  372. contextsynapse/ingestion/connectors/sap_ingestor.py +208 -0
  373. contextsynapse/ingestion/content_detector.py +80 -0
  374. contextsynapse/ingestion/content_monitor.py +502 -0
  375. contextsynapse/ingestion/dedup.py +106 -0
  376. contextsynapse/ingestion/feed.py +640 -0
  377. contextsynapse/ingestion/filters.py +461 -0
  378. contextsynapse/ingestion/graph_builder.py +705 -0
  379. contextsynapse/ingestion/input_classifier.py +307 -0
  380. contextsynapse/ingestion/job_manager.py +571 -0
  381. contextsynapse/ingestion/llm_extractor.py +610 -0
  382. contextsynapse/ingestion/orchestrator.py +116 -0
  383. contextsynapse/ingestion/parsers/__init__.py +19 -0
  384. contextsynapse/ingestion/parsers/graph_parser_registry.py +103 -0
  385. contextsynapse/ingestion/parsers/graphml_parser.py +141 -0
  386. contextsynapse/ingestion/parsers/jsonld_parser.py +171 -0
  387. contextsynapse/ingestion/parsers/rdf_parser.py +150 -0
  388. contextsynapse/ingestion/pipeline_context.py +228 -0
  389. contextsynapse/ingestion/pipeline_store.py +1195 -0
  390. contextsynapse/ingestion/queue.py +133 -0
  391. contextsynapse/ingestion/reprocess.py +179 -0
  392. contextsynapse/ingestion/scenario_router.py +224 -0
  393. contextsynapse/ingestion/schema_extractor.py +759 -0
  394. contextsynapse/ingestion/schema_validator.py +274 -0
  395. contextsynapse/ingestion/sdlc_ingest.py +87 -0
  396. contextsynapse/ingestion/smart_ingest.py +2451 -0
  397. contextsynapse/ingestion/source_connector.py +171 -0
  398. contextsynapse/ingestion/stage_executor.py +2095 -0
  399. contextsynapse/ingestion/stages/__init__.py +9 -0
  400. contextsynapse/ingestion/stages/tabular_to_graph.py +289 -0
  401. contextsynapse/ingestion/strategies.py +386 -0
  402. contextsynapse/ingestion/universal/__init__.py +4 -0
  403. contextsynapse/ingestion/universal/_operator_registry.py +59 -0
  404. contextsynapse/ingestion/universal/ingest_content.py +32 -0
  405. contextsynapse/ingestion/universal/operators/__init__.py +1 -0
  406. contextsynapse/ingestion/universal/operators/base.py +19 -0
  407. contextsynapse/ingestion/universal/operators/build_edges.py +75 -0
  408. contextsynapse/ingestion/universal/operators/cluster_topics.py +397 -0
  409. contextsynapse/ingestion/universal/operators/deduplicate.py +45 -0
  410. contextsynapse/ingestion/universal/operators/detect_signals.py +90 -0
  411. contextsynapse/ingestion/universal/operators/embed.py +80 -0
  412. contextsynapse/ingestion/universal/operators/extract_entities.py +253 -0
  413. contextsynapse/ingestion/universal/operators/extract_preferences.py +117 -0
  414. contextsynapse/ingestion/universal/operators/filter_content.py +69 -0
  415. contextsynapse/ingestion/universal/operators/index_bm25.py +52 -0
  416. contextsynapse/ingestion/universal/operators/infer_domains.py +69 -0
  417. contextsynapse/ingestion/universal/operators/link_cross_reference.py +210 -0
  418. contextsynapse/ingestion/universal/operators/parse_records.py +66 -0
  419. contextsynapse/ingestion/universal/operators/resolve_entities.py +261 -0
  420. contextsynapse/ingestion/universal/operators/scanners/__init__.py +16 -0
  421. contextsynapse/ingestion/universal/operators/scanners/edges.py +173 -0
  422. contextsynapse/ingestion/universal/operators/scanners/git.py +187 -0
  423. contextsynapse/ingestion/universal/operators/scanners/github.py +135 -0
  424. contextsynapse/ingestion/universal/operators/scanners/github_api.py +509 -0
  425. contextsynapse/ingestion/universal/operators/scanners/helpers.py +67 -0
  426. contextsynapse/ingestion/universal/operators/scanners/jira.py +65 -0
  427. contextsynapse/ingestion/universal/operators/scanners/llm_enrichment.py +82 -0
  428. contextsynapse/ingestion/universal/operators/scanners/repo.py +149 -0
  429. contextsynapse/ingestion/universal/operators/sdlc_scan.py +213 -0
  430. contextsynapse/ingestion/universal/operators/spec_scanner.py +149 -0
  431. contextsynapse/ingestion/universal/operators/store_documents.py +72 -0
  432. contextsynapse/ingestion/universal/operators/synthesize_cu.py +180 -0
  433. contextsynapse/ingestion/universal/operators/validate_gate.py +65 -0
  434. contextsynapse/ingestion/universal/parsers/__init__.py +5 -0
  435. contextsynapse/ingestion/universal/parsers/record_parser.py +203 -0
  436. contextsynapse/ingestion/universal/parsers/turn_parser.py +329 -0
  437. contextsynapse/ingestion/universal/shared_signals.py +161 -0
  438. contextsynapse/ingestion/universal/stage_executor.py +219 -0
  439. contextsynapse/ingestion/web_crawler.py +265 -0
  440. contextsynapse/intelligence/__init__.py +53 -0
  441. contextsynapse/intelligence/alerts.py +198 -0
  442. contextsynapse/intelligence/collector.py +655 -0
  443. contextsynapse/intelligence/comparison.py +198 -0
  444. contextsynapse/intelligence/config.py +56 -0
  445. contextsynapse/intelligence/conflict_detector.py +182 -0
  446. contextsynapse/intelligence/context_gaps.py +192 -0
  447. contextsynapse/intelligence/context_radar.py +135 -0
  448. contextsynapse/intelligence/correlation_engine.py +416 -0
  449. contextsynapse/intelligence/correlation_template.py +120 -0
  450. contextsynapse/intelligence/dedup.py +296 -0
  451. contextsynapse/intelligence/event_bus.py +91 -0
  452. contextsynapse/intelligence/feedback_loop.py +101 -0
  453. contextsynapse/intelligence/freshness.py +307 -0
  454. contextsynapse/intelligence/fundamental_feed.py +551 -0
  455. contextsynapse/intelligence/geo_data.py +170 -0
  456. contextsynapse/intelligence/geo_reference.py +112 -0
  457. contextsynapse/intelligence/graph_fusion.py +465 -0
  458. contextsynapse/intelligence/heuristics.py +176 -0
  459. contextsynapse/intelligence/impact_tracker.py +336 -0
  460. contextsynapse/intelligence/macro_indicators.py +319 -0
  461. contextsynapse/intelligence/ocr.py +104 -0
  462. contextsynapse/intelligence/persistence.py +267 -0
  463. contextsynapse/intelligence/pipeline_health.py +130 -0
  464. contextsynapse/intelligence/price_feed.py +221 -0
  465. contextsynapse/intelligence/rate_limiter.py +130 -0
  466. contextsynapse/intelligence/reactive.py +411 -0
  467. contextsynapse/intelligence/runtime_context.py +390 -0
  468. contextsynapse/intelligence/sentiment_decay.py +426 -0
  469. contextsynapse/intelligence/session.py +277 -0
  470. contextsynapse/intelligence/signal_hierarchy.py +438 -0
  471. contextsynapse/intelligence/signals.py +185 -0
  472. contextsynapse/intelligence/source_watcher.py +132 -0
  473. contextsynapse/intelligence/table_enricher.py +256 -0
  474. contextsynapse/intelligence/tagger.py +319 -0
  475. contextsynapse/intelligence/timeseries.py +209 -0
  476. contextsynapse/intelligence/tracker.py +318 -0
  477. contextsynapse/intelligence/watchdog.py +392 -0
  478. contextsynapse/intelligence/watchdog_manager.py +263 -0
  479. contextsynapse/intelligence/webhook_receiver.py +165 -0
  480. contextsynapse/intelligence/weight_learner.py +234 -0
  481. contextsynapse/llm/__init__.py +8 -0
  482. contextsynapse/llm/client.py +348 -0
  483. contextsynapse/llm/rate_limiter.py +166 -0
  484. contextsynapse/logging_config.py +2 -0
  485. contextsynapse/marketplace/__init__.py +7 -0
  486. contextsynapse/marketplace/registry.py +173 -0
  487. contextsynapse/mcp/__init__.py +11 -0
  488. contextsynapse/mcp/__main__.py +4 -0
  489. contextsynapse/mcp/auth_middleware.py +212 -0
  490. contextsynapse/mcp/connection_pool.py +128 -0
  491. contextsynapse/mcp/server.py +986 -0
  492. contextsynapse/mcp/session_context.py +67 -0
  493. contextsynapse/memory/__init__.py +34 -0
  494. contextsynapse/memory/engine.py +475 -0
  495. contextsynapse/memory/temporal.py +327 -0
  496. contextsynapse/metadata/__init__.py +12 -0
  497. contextsynapse/metadata/metadata_db.py +272 -0
  498. contextsynapse/metadata/metadata_query_tool.py +300 -0
  499. contextsynapse/metadata/metadata_tracker.py +425 -0
  500. contextsynapse/models/__init__.py +27 -0
  501. contextsynapse/models/embedding_service.py +360 -0
  502. contextsynapse/models/model_registry.py +228 -0
  503. contextsynapse/multiworker.py +2 -0
  504. contextsynapse/pipelines/__init__.py +1 -0
  505. contextsynapse/pipelines/executor.py +851 -0
  506. contextsynapse/pipelines/filters.py +189 -0
  507. contextsynapse/pipelines/models.py +176 -0
  508. contextsynapse/pipelines/scheduler.py +268 -0
  509. contextsynapse/playground/__init__.py +1 -0
  510. contextsynapse/playground/templates.py +85 -0
  511. contextsynapse/plugins/__init__.py +43 -0
  512. contextsynapse/plugins/api.py +66 -0
  513. contextsynapse/plugins/base.py +350 -0
  514. contextsynapse/plugins/domain_interface.py +508 -0
  515. contextsynapse/plugins/domain_loader.py +241 -0
  516. contextsynapse/plugins/loader.py +148 -0
  517. contextsynapse/plugins/registry.py +265 -0
  518. contextsynapse/project/__init__.py +11 -0
  519. contextsynapse/project/code_context.py +807 -0
  520. contextsynapse/project/connectors/__init__.py +4 -0
  521. contextsynapse/project/connectors/github_connector.py +113 -0
  522. contextsynapse/project/connectors/jira_connector.py +160 -0
  523. contextsynapse/project/graph.py +1023 -0
  524. contextsynapse/project/project_context.py +545 -0
  525. contextsynapse/project/scanner_registry.py +61 -0
  526. contextsynapse/project/schema_manager.py +322 -0
  527. contextsynapse/project/schemas/knowledge.yaml +59 -0
  528. contextsynapse/project/schemas/sdlc.yaml +369 -0
  529. contextsynapse/project/sdlc_schema.py +172 -0
  530. contextsynapse/project/spec_parser.py +85 -0
  531. contextsynapse/project/task_lock.py +181 -0
  532. contextsynapse/project/task_stream.py +203 -0
  533. contextsynapse/project/team_tools.py +537 -0
  534. contextsynapse/py.typed +0 -0
  535. contextsynapse/realtime/__init__.py +6 -0
  536. contextsynapse/realtime/collaboration.py +147 -0
  537. contextsynapse/rules/__init__.py +10 -0
  538. contextsynapse/rules/engine.py +116 -0
  539. contextsynapse/rules/evaluators/__init__.py +6 -0
  540. contextsynapse/rules/evaluators/aggregate.py +205 -0
  541. contextsynapse/rules/evaluators/custom.py +170 -0
  542. contextsynapse/rules/evaluators/pre_action.py +125 -0
  543. contextsynapse/rules/evaluators/temporal.py +129 -0
  544. contextsynapse/rules/loader.py +52 -0
  545. contextsynapse/rules/models.py +124 -0
  546. contextsynapse/rules/universal.py +393 -0
  547. contextsynapse/scheduler.py +229 -0
  548. contextsynapse/schema/__init__.py +42 -0
  549. contextsynapse/schema/compiler.py +252 -0
  550. contextsynapse/schema/composer.py +158 -0
  551. contextsynapse/schema/dedup.py +73 -0
  552. contextsynapse/schema/derivation.py +172 -0
  553. contextsynapse/schema/sdl.py +326 -0
  554. contextsynapse/schema/validation_gate.py +124 -0
  555. contextsynapse/sdk/__init__.py +63 -0
  556. contextsynapse/sdk/agent.py +214 -0
  557. contextsynapse/sdk/client.py +269 -0
  558. contextsynapse/sdk/exceptions.py +23 -0
  559. contextsynapse/sdk/models.py +78 -0
  560. contextsynapse/sdk/session.py +408 -0
  561. contextsynapse/sdk/tracked.py +218 -0
  562. contextsynapse/sdk/worker.py +266 -0
  563. contextsynapse/sdk/workspace_client.py +98 -0
  564. contextsynapse/sdk/ws.py +41 -0
  565. contextsynapse/search/__init__.py +20 -0
  566. contextsynapse/search/embedding_cache.py +140 -0
  567. contextsynapse/search/enhanced_search.py +152 -0
  568. contextsynapse/search/fulltext.py +130 -0
  569. contextsynapse/search/graph_search.py +634 -0
  570. contextsynapse/search/lmdb_index.py +1218 -0
  571. contextsynapse/search/nl_to_aiql.py +199 -0
  572. contextsynapse/search/rag.py +1346 -0
  573. contextsynapse/search/rag_cache.py +167 -0
  574. contextsynapse/search/reasoning_chain.py +231 -0
  575. contextsynapse/search/redis_search.py +207 -0
  576. contextsynapse/search/retrieval_quality.py +314 -0
  577. contextsynapse/search/semantic_query.py +455 -0
  578. contextsynapse/search/text_resolver.py +159 -0
  579. contextsynapse/search/whoosh_search.py +373 -0
  580. contextsynapse/security/__init__.py +17 -0
  581. contextsynapse/security/audit.py +297 -0
  582. contextsynapse/security/audit_logger.py +97 -0
  583. contextsynapse/security/audit_trail.py +270 -0
  584. contextsynapse/security/auth_unified.py +284 -0
  585. contextsynapse/security/auto_tagger.py +230 -0
  586. contextsynapse/security/data_security.py +218 -0
  587. contextsynapse/security/encryption.py +207 -0
  588. contextsynapse/security/identity.py +447 -0
  589. contextsynapse/security/jwt_identity.py +59 -0
  590. contextsynapse/security/middleware.py +336 -0
  591. contextsynapse/security/pii.py +233 -0
  592. contextsynapse/security/rbac.py +96 -0
  593. contextsynapse/security/rls.py +205 -0
  594. contextsynapse/security/sanitize.py +233 -0
  595. contextsynapse/security/scoped_encryption.py +131 -0
  596. contextsynapse/security/tenant.py +401 -0
  597. contextsynapse/shield/__init__.py +21 -0
  598. contextsynapse/shield/anomaly.py +56 -0
  599. contextsynapse/shield/permissions.py +99 -0
  600. contextsynapse/shield/profile.py +103 -0
  601. contextsynapse/shield/shield.py +108 -0
  602. contextsynapse/shield/trust_engine.py +69 -0
  603. contextsynapse/skills/__init__.py +14 -0
  604. contextsynapse/skills/engine.py +233 -0
  605. contextsynapse/skills/loader.py +59 -0
  606. contextsynapse/skills/models.py +139 -0
  607. contextsynapse/storage/__init__.py +7 -0
  608. contextsynapse/storage/cache.py +208 -0
  609. contextsynapse/storage/columnar_store_v2.py +178 -0
  610. contextsynapse/storage/csr_graph_storage.py +587 -0
  611. contextsynapse/storage/csr_store.py +388 -0
  612. contextsynapse/storage/document_store.py +491 -0
  613. contextsynapse/storage/hnsw_index.py +294 -0
  614. contextsynapse/storage/hybrid_store_v2.py +278 -0
  615. contextsynapse/storage/lmdb_graph_storage.py +366 -0
  616. contextsynapse/storage/migration.py +279 -0
  617. contextsynapse/storage/namespace_store.py +346 -0
  618. contextsynapse/storage/pure_graph_storage.py +185 -0
  619. contextsynapse/storage/redis_graph_adapter.py +186 -0
  620. contextsynapse/storage/redis_graph_storage.py +375 -0
  621. contextsynapse/storage/router/__init__.py +29 -0
  622. contextsynapse/storage/router/duckdb_store.py +398 -0
  623. contextsynapse/storage/router/factory.py +83 -0
  624. contextsynapse/storage/router/interface.py +125 -0
  625. contextsynapse/storage/router/postgres_store.py +339 -0
  626. contextsynapse/storage/router/resolver.py +192 -0
  627. contextsynapse/storage/router/router.py +300 -0
  628. contextsynapse/storage/single_file_storage.py +391 -0
  629. contextsynapse/storage/storage_manager.py +214 -0
  630. contextsynapse/storage/storage_query_optimizer.py +314 -0
  631. contextsynapse/storage/wal.py +359 -0
  632. contextsynapse/temporal/__init__.py +0 -0
  633. contextsynapse/temporal/storage.py +106 -0
  634. contextsynapse/tests/__init__.py +11 -0
  635. contextsynapse/tests/regression/__init__.py +1 -0
  636. contextsynapse/tests/regression/add_metadata.py +395 -0
  637. contextsynapse/tests/regression/add_skip_flags.py +85 -0
  638. contextsynapse/tests/regression/cases/test_basic_create_node.yaml +20 -0
  639. contextsynapse/tests/regression/cases/test_basic_select.yaml +21 -0
  640. contextsynapse/tests/regression/cases/test_blockchain_audit_trail.yaml +37 -0
  641. contextsynapse/tests/regression/cases/test_blockchain_audit_trail_filtered.yaml +39 -0
  642. contextsynapse/tests/regression/cases/test_blockchain_blocks_range.yaml +39 -0
  643. contextsynapse/tests/regression/cases/test_blockchain_get_block.yaml +35 -0
  644. contextsynapse/tests/regression/cases/test_blockchain_latest_block.yaml +35 -0
  645. contextsynapse/tests/regression/cases/test_blockchain_length.yaml +37 -0
  646. contextsynapse/tests/regression/cases/test_blockchain_merkle_root.yaml +37 -0
  647. contextsynapse/tests/regression/cases/test_blockchain_verify.yaml +35 -0
  648. contextsynapse/tests/regression/cases/test_blockchain_verify_block.yaml +35 -0
  649. contextsynapse/tests/regression/cases/test_create_edge.yaml +28 -0
  650. contextsynapse/tests/regression/cases/test_delete_edge_basic.yaml +29 -0
  651. contextsynapse/tests/regression/cases/test_delete_node_basic.yaml +29 -0
  652. contextsynapse/tests/regression/cases/test_evaluation_mrr.yaml +40 -0
  653. contextsynapse/tests/regression/cases/test_evaluation_ndcg.yaml +40 -0
  654. contextsynapse/tests/regression/cases/test_evaluation_precision.yaml +40 -0
  655. contextsynapse/tests/regression/cases/test_evaluation_recall.yaml +40 -0
  656. contextsynapse/tests/regression/cases/test_group_by_basic.yaml +56 -0
  657. contextsynapse/tests/regression/cases/test_match_node_basic.yaml +29 -0
  658. contextsynapse/tests/regression/cases/test_match_node_with_order_limit.yaml +31 -0
  659. contextsynapse/tests/regression/cases/test_namespace_switch.yaml +18 -0
  660. contextsynapse/tests/regression/cases/test_pipeline_cascaded.yaml +61 -0
  661. contextsynapse/tests/regression/cases/test_pipeline_fixed_size_chunking.yaml +48 -0
  662. contextsynapse/tests/regression/cases/test_pipeline_gpt4_chunking.yaml +54 -0
  663. contextsynapse/tests/regression/cases/test_pipeline_hybrid_indexing.yaml +57 -0
  664. contextsynapse/tests/regression/cases/test_pipeline_large_embedding.yaml +52 -0
  665. contextsynapse/tests/regression/cases/test_pipeline_paragraph_chunking.yaml +49 -0
  666. contextsynapse/tests/regression/cases/test_pipeline_recursive_chunking.yaml +47 -0
  667. contextsynapse/tests/regression/cases/test_pipeline_run_pipeline.yaml +56 -0
  668. contextsynapse/tests/regression/cases/test_pipeline_section_based_chunking.yaml +53 -0
  669. contextsynapse/tests/regression/cases/test_pipeline_semantic_chunking.yaml +51 -0
  670. contextsynapse/tests/regression/cases/test_pipeline_with_entity_extraction.yaml +57 -0
  671. contextsynapse/tests/regression/cases/test_pipeline_with_relationships.yaml +62 -0
  672. contextsynapse/tests/regression/cases/test_rag_evaluation.yaml +38 -0
  673. contextsynapse/tests/regression/cases/test_rag_graph_bfs_with_eval.yaml +48 -0
  674. contextsynapse/tests/regression/cases/test_rag_graph_centrality.yaml +40 -0
  675. contextsynapse/tests/regression/cases/test_rag_graph_centrality_eval.yaml +35 -0
  676. contextsynapse/tests/regression/cases/test_rag_graph_community.yaml +40 -0
  677. contextsynapse/tests/regression/cases/test_rag_graph_community_eval.yaml +35 -0
  678. contextsynapse/tests/regression/cases/test_rag_graph_dfs_with_eval.yaml +35 -0
  679. contextsynapse/tests/regression/cases/test_rag_graph_multi_hop_eval.yaml +35 -0
  680. contextsynapse/tests/regression/cases/test_rag_graph_path.yaml +40 -0
  681. contextsynapse/tests/regression/cases/test_rag_graph_search_basic.yaml +38 -0
  682. contextsynapse/tests/regression/cases/test_rag_graph_search_bfs.yaml +52 -0
  683. contextsynapse/tests/regression/cases/test_rag_graph_search_dfs.yaml +41 -0
  684. contextsynapse/tests/regression/cases/test_rag_graph_search_multi_hop.yaml +43 -0
  685. contextsynapse/tests/regression/cases/test_rag_graph_search_shortest_path.yaml +41 -0
  686. contextsynapse/tests/regression/cases/test_rag_graph_search_weighted.yaml +43 -0
  687. contextsynapse/tests/regression/cases/test_rag_graph_shortest_path_eval.yaml +35 -0
  688. contextsynapse/tests/regression/cases/test_rag_graph_traversal.yaml +30 -0
  689. contextsynapse/tests/regression/cases/test_rag_graph_weighted_eval.yaml +35 -0
  690. contextsynapse/tests/regression/cases/test_rag_hybrid_search.yaml +33 -0
  691. contextsynapse/tests/regression/cases/test_rag_hybrid_with_prompt_embedding.yaml +44 -0
  692. contextsynapse/tests/regression/cases/test_rag_keyword_search.yaml +32 -0
  693. contextsynapse/tests/regression/cases/test_rag_multi_hop_graph.yaml +32 -0
  694. contextsynapse/tests/regression/cases/test_rag_path_based.yaml +34 -0
  695. contextsynapse/tests/regression/cases/test_rag_profile_based.yaml +32 -0
  696. contextsynapse/tests/regression/cases/test_rag_vector_search.yaml +32 -0
  697. contextsynapse/tests/regression/cases/test_rag_weighted_graph.yaml +34 -0
  698. contextsynapse/tests/regression/cases/test_rag_weighted_hybrid.yaml +31 -0
  699. contextsynapse/tests/regression/cases/test_semantic_boundary_chunking.yaml +57 -0
  700. contextsynapse/tests/regression/cases/test_semantic_boundary_detection.yaml +33 -0
  701. contextsynapse/tests/regression/cases/test_semantic_hash_function.yaml +30 -0
  702. contextsynapse/tests/regression/cases/test_semantic_hash_with_model.yaml +31 -0
  703. contextsynapse/tests/regression/cases/test_tcs_ingestion_fixed_size.yaml +44 -0
  704. contextsynapse/tests/regression/cases/test_tcs_ingestion_recursive.yaml +46 -0
  705. contextsynapse/tests/regression/cases/test_tcs_ingestion_section.yaml +46 -0
  706. contextsynapse/tests/regression/cases/test_tcs_ingestion_semantic.yaml +44 -0
  707. contextsynapse/tests/regression/cases/test_time_travel_as_of.yaml +35 -0
  708. contextsynapse/tests/regression/cases/test_time_travel_at_commit.yaml +33 -0
  709. contextsynapse/tests/regression/cases/test_time_travel_at_timestamp.yaml +33 -0
  710. contextsynapse/tests/regression/cases/test_time_travel_for_system_time_all.yaml +37 -0
  711. contextsynapse/tests/regression/cases/test_time_travel_versions_between.yaml +39 -0
  712. contextsynapse/tests/regression/cases/test_traverse_basic.yaml +31 -0
  713. contextsynapse/tests/regression/cases/test_traverse_max_depth.yaml +34 -0
  714. contextsynapse/tests/regression/cases/test_update_edge_basic.yaml +29 -0
  715. contextsynapse/tests/regression/evaluation_metrics.py +227 -0
  716. contextsynapse/tests/regression/regression_runner.py +1081 -0
  717. contextsynapse/tests/regression/setup_regression_data.py +212 -0
  718. contextsynapse/tests/regression/test_basic_only.py +84 -0
  719. contextsynapse/tests/regression/test_namespace_creation.py +29 -0
  720. contextsynapse/tests/regression/test_single_fix.py +61 -0
  721. contextsynapse/tests/regression/test_single_query.py +20 -0
  722. contextsynapse/tests/regression/validate_test_syntax.py +179 -0
  723. contextsynapse/tests/test_advanced_features.py +347 -0
  724. contextsynapse/tests/test_aiql_summary.py +283 -0
  725. contextsynapse/tests/test_all_aiql_queries.py +948 -0
  726. contextsynapse/tests/test_all_create_queries.py +213 -0
  727. contextsynapse/tests/test_api_vs_native.py +280 -0
  728. contextsynapse/tests/test_chunking_dedup.py +199 -0
  729. contextsynapse/tests/test_connectors.py +171 -0
  730. contextsynapse/tests/test_edges_and_match.py +202 -0
  731. contextsynapse/tests/test_extraction_pipeline_integration.py +581 -0
  732. contextsynapse/tests/test_name_property.py +224 -0
  733. contextsynapse/tests/test_node_edge_metadata.py +250 -0
  734. contextsynapse/tests/test_playground_conn.py +114 -0
  735. contextsynapse/tests/test_query_cache.py +213 -0
  736. contextsynapse/tests/test_source_policy_auth.py +348 -0
  737. contextsynapse/tests/test_temporal_and_domain.py +224 -0
  738. contextsynapse/tests/test_time_travel_e2e.py +571 -0
  739. contextsynapse/tests/test_traversal_and_aggregation.py +275 -0
  740. contextsynapse/tools/__init__.py +36 -0
  741. contextsynapse/tools/a2a_tools.py +106 -0
  742. contextsynapse/tools/cognition_tools.py +118 -0
  743. contextsynapse/tools/context.py +905 -0
  744. contextsynapse/tools/formatting.py +92 -0
  745. contextsynapse/tools/gateway_tools.py +186 -0
  746. contextsynapse/tools/graph.py +1700 -0
  747. contextsynapse/tools/intelligence.py +235 -0
  748. contextsynapse/tools/memory.py +118 -0
  749. contextsynapse/tools/registry.py +373 -0
  750. contextsynapse/tools/shield_tools.py +159 -0
  751. contextsynapse/tools/tasks.py +341 -0
  752. contextsynapse/tools/workspace_tools.py +124 -0
  753. contextsynapse/utils/__init__.py +15 -0
  754. contextsynapse/utils/config_loader.py +198 -0
  755. contextsynapse/utils/env_loader.py +156 -0
  756. contextsynapse/utils/file_utils.py +288 -0
  757. contextsynapse/utils/logging_optimizer.py +119 -0
  758. contextsynapse/vector/__init__.py +30 -0
  759. contextsynapse/vector/chroma_vector_store.py +152 -0
  760. contextsynapse/vector/custom_vector_store.py +537 -0
  761. contextsynapse/vector/faiss_vector_store.py +296 -0
  762. contextsynapse/vector/qdrant_vector_store.py +163 -0
  763. contextsynapse/vector/vector_db.py +953 -0
  764. contextsynapse/vector/vector_db_manager.py +197 -0
  765. contextsynapse/workspace/__init__.py +27 -0
  766. contextsynapse/workspace/base.py +94 -0
  767. contextsynapse/workspace/git.py +228 -0
  768. contextsynapse/workspace/github.py +78 -0
  769. contextsynapse/workspace/local.py +73 -0
  770. contextsynapse/workspace/tools.py +222 -0
  771. contextsynapse-1.0.0.dist-info/METADATA +847 -0
  772. contextsynapse-1.0.0.dist-info/RECORD +776 -0
  773. contextsynapse-1.0.0.dist-info/WHEEL +5 -0
  774. contextsynapse-1.0.0.dist-info/entry_points.txt +4 -0
  775. contextsynapse-1.0.0.dist-info/licenses/LICENSE +200 -0
  776. contextsynapse-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1043 @@
1
+ """
2
+ Dynamic graph creation, caching, and metadata management.
3
+ """
4
+
5
+ import os
6
+ import json
7
+ import pickle
8
+ import logging
9
+ import threading
10
+ from typing import Dict, List, Any, Optional, Union
11
+ from pathlib import Path
12
+ from datetime import datetime, timedelta
13
+ from dataclasses import dataclass, asdict
14
+ import hashlib
15
+
16
+ import re as _re
17
+
18
+ from .hybrid_graph_storage import AIContextDB
19
+
20
+ # Ensure .env is loaded before any graph creation (critical for Redis backend)
21
+ try:
22
+ from ..utils.env_loader import initialize_env_loader
23
+ initialize_env_loader()
24
+ except Exception:
25
+ pass
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+
30
+ def _safe_dir_name(name: str) -> str:
31
+ """Sanitize a graph name for use as a filesystem directory name.
32
+
33
+ Replaces all characters invalid on Windows (<>:"/\\|?*) with underscore.
34
+ """
35
+ return _re.sub(r'[<>:"/\\|?*]', '_', name)
36
+
37
+ @dataclass
38
+ class GraphMetadata:
39
+ """Metadata for a graph instance."""
40
+ name: str
41
+ created_at: datetime
42
+ updated_at: datetime
43
+ num_nodes: int
44
+ num_edges: int
45
+ schema_file: Optional[str] = None
46
+ data_files: List[str] = None
47
+ embedding_model: Optional[str] = None
48
+ last_accessed: Optional[datetime] = None
49
+ access_count: int = 0
50
+ size_mb: float = 0.0
51
+ tags: List[str] = None
52
+ storage_backend: str = "disk" # "disk" or "redis"
53
+
54
+ def __post_init__(self):
55
+ if self.data_files is None:
56
+ self.data_files = []
57
+ if self.tags is None:
58
+ self.tags = []
59
+
60
+ class GraphRegistry:
61
+ """
62
+ Registry for managing multiple graph instances with caching and metadata.
63
+ """
64
+
65
+ def __init__(self, storage_dir: str = None):
66
+ # Use namespace-centric structure by default
67
+ if storage_dir is None:
68
+ storage_dir = "contextcore_data"
69
+ self.storage_dir = Path(storage_dir)
70
+ self.storage_dir.mkdir(exist_ok=True)
71
+
72
+ self.graphs: Dict[str, AIContextDB] = {}
73
+ self.metadata: Dict[str, GraphMetadata] = {}
74
+ self._lock = threading.RLock()
75
+ self._metadata_mtime: float = 0.0 # track file mtime for multi-worker sync
76
+ self._graph_file_mtimes: Dict[str, float] = {} # track per-graph file mtime
77
+ self.cache_config = {
78
+ 'max_graphs': int(os.environ.get('AICONTEXTDB_MAX_GRAPHS', '50')),
79
+ 'max_size_mb': int(os.environ.get('AICONTEXTDB_MAX_GRAPH_SIZE_MB', '1000')),
80
+ 'ttl_hours': 24
81
+ }
82
+
83
+ # Load existing metadata
84
+ self._load_metadata()
85
+
86
+ def create_graph(self, name: str, schema_file: Optional[str] = None,
87
+ config: Optional[Dict] = None,
88
+ schema_name: Optional[str] = None) -> AIContextDB:
89
+ """
90
+ Create a new graph instance.
91
+
92
+ Args:
93
+ name: Name of the graph
94
+ schema_file: Optional schema file path
95
+ config: Optional configuration
96
+
97
+ Returns:
98
+ AIContextDB instance
99
+ """
100
+ with self._lock:
101
+ if name in self.graphs:
102
+ return self.graphs[name]
103
+
104
+ # Let AIContextDB pick its backend (Redis if available, else CSR)
105
+ graph = AIContextDB(name, config)
106
+
107
+ if schema_file:
108
+ pass
109
+
110
+ self.graphs[name] = graph
111
+
112
+ # Create ContextMeta root node — metadata anchor for this graph
113
+ from .graph_structures import GraphNode as _GN
114
+ meta_props = {
115
+ "name": name,
116
+ "description": "",
117
+ "purpose": "knowledge_base",
118
+ "schema_summary": "",
119
+ "node_count": 0,
120
+ "edge_count": 0,
121
+ "created_at": datetime.now().isoformat(),
122
+ "updated_at": datetime.now().isoformat(),
123
+ "owner": "",
124
+ "tags": [],
125
+ }
126
+ # Include schema in ContextMeta if provided
127
+ if config and config.get("schema"):
128
+ meta_props["schema"] = config["schema"]
129
+ if schema_name:
130
+ meta_props["schema_name"] = schema_name
131
+ graph.add_node(_GN(id="_context_meta", label="ContextMeta", properties=meta_props))
132
+
133
+ # Bind namespace to schema if provided
134
+ if schema_name:
135
+ try:
136
+ from contextsynapse.project.schema_manager import get_schema_manager
137
+ get_schema_manager().bind(name, schema_name)
138
+ except Exception as e:
139
+ logger.warning("Could not bind schema '%s' to namespace '%s': %s",
140
+ schema_name, name, e)
141
+
142
+ metadata = GraphMetadata(
143
+ name=name,
144
+ created_at=datetime.now(),
145
+ updated_at=datetime.now(),
146
+ num_nodes=0,
147
+ num_edges=0,
148
+ schema_file=schema_file,
149
+ storage_backend="redis" if getattr(graph, '_using_redis_backend', False) else "disk",
150
+ )
151
+ self.metadata[name] = metadata
152
+ self._save_metadata()
153
+
154
+ # Auto-evict LRU if over limit
155
+ self._auto_cleanup()
156
+
157
+ return graph
158
+
159
+ def drop_graph(self, name: str) -> bool:
160
+ """
161
+ Drop a graph instance and its associated files.
162
+
163
+ Args:
164
+ name: Name of the graph to drop
165
+
166
+ Returns:
167
+ True if successful, False if graph doesn't exist
168
+ """
169
+ with self._lock:
170
+ if name not in self.graphs and name not in self.metadata:
171
+ return False
172
+
173
+ try:
174
+ # Remove from memory
175
+ if name in self.graphs:
176
+ del self.graphs[name]
177
+
178
+ # Remove metadata
179
+ if name in self.metadata:
180
+ del self.metadata[name]
181
+
182
+ # Remove files from disk
183
+ graph_dir = self.storage_dir / name
184
+ if graph_dir.exists():
185
+ import shutil
186
+ shutil.rmtree(graph_dir)
187
+
188
+ # Save updated metadata
189
+ self._save_metadata()
190
+
191
+ return True
192
+
193
+ except Exception as e:
194
+ print(f"Error dropping graph '{name}': {e}")
195
+ return False
196
+
197
+ def get_graph(self, name: str, load_if_missing: bool = True) -> Optional[AIContextDB]:
198
+ """
199
+ Get a graph instance by name.
200
+
201
+ Args:
202
+ name: Name of the graph
203
+ load_if_missing: Whether to load from disk if not in memory
204
+
205
+ Returns:
206
+ AIContextDB instance or None
207
+ """
208
+ with self._lock:
209
+ self._refresh_metadata_if_stale()
210
+
211
+ if name in self.graphs:
212
+ graph = self.graphs[name]
213
+ # Skip disk reload for Redis-backed graphs — Redis is source of truth
214
+ if not getattr(graph, '_using_redis_backend', False):
215
+ if self._graph_file_is_newer(name):
216
+ logger.debug("[REGISTRY] Graph '%s' changed on disk — reloading", name)
217
+ reloaded = self._load_graph_from_disk(name)
218
+ if reloaded:
219
+ self.graphs[name] = reloaded
220
+ graph = reloaded
221
+ self._update_access_metadata(name)
222
+ return graph
223
+
224
+ if load_if_missing:
225
+ # For Redis-backed graphs: just create instance (connects to Redis, no disk load)
226
+ import os
227
+ if os.environ.get("CONTEXTSYNAPSE_REDIS_URL") or os.environ.get("AICONTEXTDB_REDIS_URL") and os.environ.get("CONTEXTSYNAPSE_GRAPH_BACKEND") or os.environ.get("AICONTEXTDB_GRAPH_BACKEND", "redis") == "redis":
228
+ graph = AIContextDB(name)
229
+ if getattr(graph, '_using_redis_backend', False):
230
+ self.graphs[name] = graph
231
+ self._auto_cleanup()
232
+ logger.debug("[REGISTRY] Graph '%s' created (Redis backend, no disk load)", name)
233
+ return graph
234
+
235
+ # Fallback: load from disk (CSR/LMDB)
236
+ if name in self.metadata:
237
+ loaded_graph = self._load_graph_from_disk(name)
238
+ if loaded_graph:
239
+ self.graphs[name] = loaded_graph
240
+ self._auto_cleanup()
241
+ return loaded_graph
242
+
243
+ return None
244
+
245
+ def list_graphs(self) -> List[Dict[str, Any]]:
246
+ """List all available graphs with metadata."""
247
+ with self._lock:
248
+ self._refresh_metadata_if_stale()
249
+ graphs_info = []
250
+ for name, metadata in self.metadata.items():
251
+ info = asdict(metadata)
252
+ info['in_memory'] = name in self.graphs
253
+ info['on_disk'] = True # assume on disk if in metadata
254
+ info['last_accessed'] = metadata.last_accessed.isoformat() if metadata.last_accessed else None
255
+ info['created_at'] = metadata.created_at.isoformat()
256
+ info['updated_at'] = metadata.updated_at.isoformat()
257
+ graphs_info.append(info)
258
+ return sorted(graphs_info, key=lambda x: x.get('last_accessed') or x.get('created_at', ''), reverse=True)
259
+
260
+ def delete_graph(self, name: str, delete_files: bool = True) -> bool:
261
+ """
262
+ Delete a graph instance.
263
+
264
+ Args:
265
+ name: Name of the graph
266
+ delete_files: Whether to delete associated files
267
+
268
+ Returns:
269
+ True if successful
270
+ """
271
+ with self._lock:
272
+ try:
273
+ # Close WAL/buffer handles before deleting
274
+ if name in self.graphs:
275
+ graph = self.graphs[name]
276
+ if hasattr(graph, 'wal') and graph.wal:
277
+ try:
278
+ graph.wal.running = False
279
+ if graph.wal.wal_file:
280
+ graph.wal.wal_file.close()
281
+ graph.wal.wal_file = None
282
+ except Exception:
283
+ pass
284
+ del self.graphs[name]
285
+ else:
286
+ # Graph not in memory — try to close any orphaned WAL file handles
287
+ # This handles the case where a graph was deleted but WAL is
288
+ # still locked by a stale file handle
289
+ safe_name = _safe_dir_name(name)
290
+ for wal_dir in [
291
+ self.storage_dir / "wal" / safe_name,
292
+ self.storage_dir / "wal" / name,
293
+ ]:
294
+ if wal_dir.exists():
295
+ for wal_file in wal_dir.glob("*.log"):
296
+ try:
297
+ # Open and immediately close to release any stale handle
298
+ with open(wal_file, "a"):
299
+ pass
300
+ except Exception:
301
+ pass
302
+
303
+ # Remove metadata
304
+ if name in self.metadata:
305
+ del self.metadata[name]
306
+
307
+ # Delete files if requested
308
+ if delete_files:
309
+ import shutil as _shutil
310
+ safe_name = _safe_dir_name(name)
311
+ # Clean namespace directories (relative to storage_dir)
312
+ for base in [
313
+ self.storage_dir / "namespaces" / safe_name,
314
+ self.storage_dir / "namespaces" / name,
315
+ self.storage_dir / safe_name,
316
+ ]:
317
+ if base.exists():
318
+ _shutil.rmtree(base, ignore_errors=True)
319
+ # Clean WAL directory
320
+ for wal in [
321
+ self.storage_dir / "wal" / safe_name,
322
+ self.storage_dir / "wal" / name,
323
+ ]:
324
+ if wal.exists():
325
+ try:
326
+ _shutil.rmtree(wal)
327
+ except PermissionError:
328
+ # WAL locked by another process — close handles and retry
329
+ for wf in wal.glob("*.log"):
330
+ try:
331
+ with open(wf, "a"):
332
+ pass
333
+ except Exception:
334
+ pass
335
+ try:
336
+ _shutil.rmtree(wal)
337
+ except Exception as e:
338
+ logger.warning("WAL cleanup failed for %s: %s (will clear on restart)", name, e)
339
+ except Exception as e:
340
+ logger.warning("WAL cleanup failed for %s: %s", name, e)
341
+
342
+ # Save updated metadata
343
+ self._save_metadata()
344
+
345
+ # Cascade: delete any context/session that uses this graph namespace
346
+ # Use storage_dir-relative context.db so it works with custom paths
347
+ context_db = str(self.storage_dir / "context.db")
348
+ try:
349
+ import sqlite3 as _sqlite3
350
+ conn = _sqlite3.connect(context_db)
351
+ conn.row_factory = _sqlite3.Row
352
+ row = conn.execute(
353
+ "SELECT context_id FROM contexts WHERE graph_namespace = ?",
354
+ (name,),
355
+ ).fetchone()
356
+ if row:
357
+ conn.execute(
358
+ "DELETE FROM contexts WHERE graph_namespace = ?",
359
+ (name,),
360
+ )
361
+ conn.commit()
362
+ # Remove from default graph
363
+ default = self.graphs.get("default")
364
+ if default and row["context_id"]:
365
+ try:
366
+ default.remove_node(row["context_id"])
367
+ except Exception:
368
+ pass # optional
369
+ conn.close()
370
+ except Exception:
371
+ pass # optional
372
+
373
+ try:
374
+ import sqlite3 as _sqlite3
375
+ conn = _sqlite3.connect(context_db)
376
+ conn.execute(
377
+ "DELETE FROM context_sessions WHERE graph_namespace = ?",
378
+ (name,),
379
+ )
380
+ conn.commit()
381
+ conn.close()
382
+ except Exception:
383
+ pass # optional
384
+
385
+ return True
386
+ except Exception as e:
387
+ print(f"Error deleting graph '{name}': {e}")
388
+ return False
389
+
390
+ def save_graph(self, name: str, create_checkpoint: bool = True) -> bool:
391
+ """
392
+ Save a graph to disk - uses namespace-centric structure for PERSISTENT namespaces.
393
+ Optionally creates a checkpoint (Git-like versioning) for rollback capability.
394
+
395
+ This method is used by BOTH direct graph operations and API operations.
396
+ It ensures the same code path is used regardless of how the graph was created/modified.
397
+
398
+ Args:
399
+ name: Name of the graph/namespace
400
+ create_checkpoint: Whether to create a checkpoint after saving (default: True)
401
+
402
+ Returns:
403
+ True if successful
404
+ """
405
+ if name not in self.graphs:
406
+ logger.error(f"[REGISTRY SAVE] Graph '{name}' not found in registry!")
407
+ return False
408
+
409
+ try:
410
+ graph = self.graphs[name]
411
+ if graph is None:
412
+ logger.error(f"[REGISTRY SAVE] Graph '{name}' is None in registry!")
413
+ return False
414
+
415
+ # Skip disk save for Redis-backed graphs — Redis is the persistence layer
416
+ if getattr(graph, '_using_redis_backend', False):
417
+ logger.debug("[REGISTRY SAVE] Skipping disk save for Redis-backed graph '%s'", name)
418
+ if name in self.metadata:
419
+ self.metadata[name].storage_backend = "redis"
420
+ self._save_metadata()
421
+ return True
422
+
423
+ # Single deterministic path — relative to storage_dir
424
+ safe_name = _safe_dir_name(name)
425
+ graph_file = self.storage_dir / "namespaces" / safe_name / "graph.json"
426
+
427
+ # Ensure directory exists
428
+ graph_file.parent.mkdir(parents=True, exist_ok=True)
429
+
430
+ # NOTE: Aliases are now query-scoped (stored in executor, not in graph)
431
+ # No need to save aliases to disk
432
+
433
+ node_count = len(graph.node_index) if hasattr(graph, 'node_index') else 0
434
+ logger.info(f"[REGISTRY SAVE] Saving graph '{name}': {node_count} nodes")
435
+
436
+ # Save graph data
437
+ success = graph.save(str(graph_file))
438
+
439
+ if success:
440
+ # Create metadata entry if it doesn't exist yet (e.g. graph
441
+ # populated via ingestion without an explicit create_graph call)
442
+ if name not in self.metadata:
443
+ self.metadata[name] = GraphMetadata(
444
+ name=name,
445
+ created_at=datetime.now(),
446
+ updated_at=datetime.now(),
447
+ num_nodes=0,
448
+ num_edges=0,
449
+ )
450
+
451
+ # Update metadata
452
+ metadata = self.metadata[name]
453
+ metadata.updated_at = datetime.now()
454
+
455
+ # Get node and edge counts — try multiple sources, take max
456
+ node_count = 0
457
+ edge_count = 0
458
+ if hasattr(graph, 'csr_storage') and graph.csr_storage:
459
+ node_count = max(node_count, graph.csr_storage.get_node_count() if hasattr(graph.csr_storage, 'get_node_count') else 0)
460
+ edge_count = max(edge_count, graph.csr_storage.get_edge_count() if hasattr(graph.csr_storage, 'get_edge_count') else 0)
461
+ if hasattr(graph, 'node_index') and graph.node_index:
462
+ node_count = max(node_count, len(graph.node_index))
463
+ if hasattr(graph, 'edge_store') and graph.edge_store:
464
+ edge_count = max(edge_count, len(graph.edge_store))
465
+ metadata.num_nodes = node_count
466
+ metadata.num_edges = edge_count
467
+
468
+ # Check actual file size (may be .h5 instead of .json)
469
+ actual_file = graph_file if graph_file.exists() else graph_file.with_suffix('.h5')
470
+ if actual_file.exists():
471
+ metadata.size_mb = actual_file.stat().st_size / (1024 * 1024)
472
+
473
+ # Track file mtime so this worker knows it wrote this version
474
+ # Check both .json and .h5 since save() may produce either format
475
+ try:
476
+ actual_file = graph_file if graph_file.exists() else graph_file.with_suffix('.h5')
477
+ if not actual_file.exists():
478
+ # Also check the parent dir for graph.h5
479
+ h5_path = graph_file.parent / "graph.h5"
480
+ if h5_path.exists():
481
+ actual_file = h5_path
482
+ self._graph_file_mtimes[name] = actual_file.stat().st_mtime
483
+ except Exception:
484
+ pass # optional
485
+
486
+ # Save metadata
487
+ self._save_metadata()
488
+
489
+ # Create checkpoint (Git-like versioning) if enabled
490
+ if create_checkpoint:
491
+ try:
492
+ from .checkpoint import checkpoint_manager
493
+ checkpoint_manager.create_checkpoint(
494
+ namespace=safe_name,
495
+ graph_file=str(graph_file),
496
+ message="Auto-save checkpoint"
497
+ )
498
+ except Exception as e:
499
+ # Don't fail save if checkpoint fails
500
+ logger.warning(f"Failed to create checkpoint: {e}")
501
+
502
+ return True
503
+
504
+ return False
505
+ except Exception as e:
506
+ print(f"Error saving graph '{name}': {e}")
507
+ return False
508
+
509
+ def load_graph(self, name: str) -> bool:
510
+ """
511
+ Load a graph from disk.
512
+
513
+ Args:
514
+ name: Name of the graph
515
+
516
+ Returns:
517
+ True if successful
518
+ """
519
+ return self._load_graph_from_disk(name) is not None
520
+
521
+ def _get_namespace_path(self, name: str) -> Optional[Path]:
522
+ """Get the path to a namespace's graph file (if it exists) without loading.
523
+
524
+ Single deterministic lookup — relative to storage_dir.
525
+ Priority: namespace-centric JSON → namespace-centric H5 → old flat-structure fallback.
526
+ """
527
+ try:
528
+ safe_name = _safe_dir_name(name)
529
+ ns_dir = self.storage_dir / "namespaces" / safe_name
530
+ candidates = [
531
+ ns_dir / "graph.json",
532
+ ns_dir / "graph.h5",
533
+ # Old flat structure fallback
534
+ self.storage_dir / safe_name / "graph.json",
535
+ self.storage_dir / safe_name / "graph.h5",
536
+ ]
537
+ for c in candidates:
538
+ if c.exists():
539
+ return c
540
+ return None
541
+ except Exception as e:
542
+ logger.debug(f"Error getting namespace path for '{name}': {e}")
543
+ return None
544
+
545
+ def _namespace_exists_on_disk(self, name: str) -> bool:
546
+ """Check if namespace exists on disk (fast check, no loading)."""
547
+ graph_file = self._get_namespace_path(name)
548
+ return graph_file is not None and graph_file.exists()
549
+
550
+ def _load_graph_from_disk(self, name: str) -> Optional[AIContextDB]:
551
+ """Load a graph from disk - supports both old and namespace-centric structures."""
552
+ try:
553
+ graph_file = self._get_namespace_path(name)
554
+
555
+ if not graph_file or not graph_file.exists():
556
+ return None
557
+
558
+ # Create graph instance with CSR storage enabled by default
559
+ graph = AIContextDB(name, storage_backend='csr')
560
+
561
+ # Load graph data
562
+ success = graph.load(str(graph_file))
563
+
564
+ if success:
565
+ node_count = len(graph.node_index) if hasattr(graph, 'node_index') else 0
566
+ logger.debug(f"[LOAD] Loaded graph '{name}': {node_count} nodes")
567
+
568
+ # Store in registry
569
+ self.graphs[name] = graph
570
+
571
+ # Track file mtime for multi-worker change detection
572
+ try:
573
+ self._graph_file_mtimes[name] = graph_file.stat().st_mtime
574
+ except Exception:
575
+ pass # optional
576
+
577
+ # Update access metadata
578
+ self._update_access_metadata(name)
579
+
580
+ return graph
581
+
582
+ return None
583
+ except Exception as e:
584
+ logger.error(f"Error loading graph '{name}': {e}")
585
+ return None
586
+
587
+ def get_graph_for_request(self, name: str) -> AIContextDB:
588
+ """Get graph instance for a request — delegates to get_graph().
589
+
590
+ Creates and caches a transient instance if the graph isn't in metadata
591
+ (e.g. Redis-backed graphs created at runtime). The cache ensures all
592
+ callers within the same process share the same instance — critical for
593
+ multi-agent experiments where agents must see each other's writes.
594
+ """
595
+ graph = self.get_graph(name, load_if_missing=True)
596
+ if graph is None:
597
+ # Graph doesn't exist in metadata or on disk — create a transient one.
598
+ # Let AIContextDB pick its backend (Redis if available, else CSR).
599
+ graph = AIContextDB(name)
600
+ # Cache it so subsequent calls return the SAME instance
601
+ with self._lock:
602
+ self.graphs[name] = graph
603
+ return graph
604
+
605
+ def _update_access_metadata(self, name: str):
606
+ """Update access metadata for a graph."""
607
+ if name in self.metadata:
608
+ metadata = self.metadata[name]
609
+ metadata.last_accessed = datetime.now()
610
+ metadata.access_count += 1
611
+
612
+ def _load_metadata(self):
613
+ """Load metadata from LMDB (fast) or disk JSON (fallback)."""
614
+ # Try LMDB first — microsecond reads
615
+ if self._load_metadata_lmdb():
616
+ # Prune stale entries — skip Redis-backed graphs (they have no disk file by design)
617
+ stale = [
618
+ n for n in self.metadata
619
+ if self.metadata[n].storage_backend != "redis"
620
+ and not self._namespace_exists_on_disk(n)
621
+ ]
622
+ for n in stale:
623
+ logger.debug(f"[REGISTRY] Pruning stale metadata entry '{n}' (no file on disk)")
624
+ del self.metadata[n]
625
+ if stale:
626
+ logger.info(f"[REGISTRY] Pruned {len(stale)} stale metadata entries")
627
+ # Write back to LMDB so they don't reload next startup
628
+ self._save_metadata_lmdb()
629
+ return
630
+
631
+ # Fallback: JSON file
632
+ metadata_file = self.storage_dir / "metadata.json"
633
+
634
+ if metadata_file.exists():
635
+ try:
636
+ with open(metadata_file, 'r') as f:
637
+ data = json.load(f)
638
+
639
+ for name, meta_data in data.items():
640
+ meta_data['created_at'] = datetime.fromisoformat(meta_data['created_at'])
641
+ meta_data['updated_at'] = datetime.fromisoformat(meta_data['updated_at'])
642
+ if meta_data.get('last_accessed'):
643
+ meta_data['last_accessed'] = datetime.fromisoformat(meta_data['last_accessed'])
644
+ self.metadata[name] = GraphMetadata(**meta_data)
645
+
646
+ # Prune stale entries — if graph file doesn't exist on disk, remove
647
+ # (skip Redis-backed graphs — they have no disk file by design)
648
+ stale = [
649
+ n for n in self.metadata
650
+ if self.metadata[n].storage_backend != "redis"
651
+ and not self._namespace_exists_on_disk(n)
652
+ ]
653
+ for n in stale:
654
+ logger.debug(f"[REGISTRY] Pruning stale metadata entry '{n}' (no file on disk)")
655
+ del self.metadata[n]
656
+ if stale:
657
+ logger.info(f"[REGISTRY] Pruned {len(stale)} stale metadata entries")
658
+ self._save_metadata()
659
+
660
+ # Track file mtime so we can detect changes from other workers
661
+ if metadata_file.exists():
662
+ self._metadata_mtime = metadata_file.stat().st_mtime
663
+ except Exception as e:
664
+ print(f"Error loading metadata: {e}")
665
+
666
+ def _graph_file_is_newer(self, name: str) -> bool:
667
+ """Check if another worker has saved a newer version of this graph to disk."""
668
+ try:
669
+ safe_name = _safe_dir_name(name)
670
+ base = self.storage_dir / "namespaces" / safe_name
671
+ # Check both .json and .h5 formats
672
+ for ext in ("graph.h5", "graph.json"):
673
+ graph_file = base / ext
674
+ if graph_file.exists():
675
+ current_mtime = graph_file.stat().st_mtime
676
+ last_known = self._graph_file_mtimes.get(name, 0.0)
677
+ if current_mtime > last_known:
678
+ return True
679
+ return False
680
+ except Exception:
681
+ return False
682
+
683
+ def _refresh_metadata_if_stale(self):
684
+ """Re-read metadata.json if another worker has written to it.
685
+
686
+ Called before read operations (list_graphs, get_graph) so that
687
+ multi-worker deployments stay consistent without Redis.
688
+ """
689
+ metadata_file = self.storage_dir / "metadata.json"
690
+ try:
691
+ if metadata_file.exists():
692
+ current_mtime = metadata_file.stat().st_mtime
693
+ if current_mtime > self._metadata_mtime:
694
+ logger.debug("[REGISTRY] metadata.json changed on disk — reloading")
695
+ old_names = set(self.metadata.keys())
696
+ self._load_metadata()
697
+ new_names = set(self.metadata.keys())
698
+ # Evict in-memory graphs that were deleted by another worker
699
+ for removed in old_names - new_names:
700
+ self.graphs.pop(removed, None)
701
+ except Exception:
702
+ pass # optional
703
+
704
+ def _save_metadata(self):
705
+ """Save metadata to disk + LMDB."""
706
+ metadata_file = self.storage_dir / "metadata.json"
707
+
708
+ try:
709
+ data = {}
710
+ for name, metadata in self.metadata.items():
711
+ meta_dict = asdict(metadata)
712
+ meta_dict['created_at'] = metadata.created_at.isoformat()
713
+ meta_dict['updated_at'] = metadata.updated_at.isoformat()
714
+ if metadata.last_accessed:
715
+ meta_dict['last_accessed'] = metadata.last_accessed.isoformat()
716
+ data[name] = meta_dict
717
+
718
+ with open(metadata_file, 'w', encoding='utf-8') as f:
719
+ json.dump(data, f, indent=2)
720
+
721
+ # Update tracked mtime so we don't re-read our own write
722
+ self._metadata_mtime = metadata_file.stat().st_mtime
723
+ except Exception as e:
724
+ print(f"Error saving metadata: {e}")
725
+
726
+ # Also persist to LMDB for fast reads
727
+ try:
728
+ self._save_metadata_lmdb()
729
+ except Exception:
730
+ pass
731
+
732
+ def _get_lmdb_meta_env(self):
733
+ """Get or create LMDB environment for metadata."""
734
+ if not hasattr(self, '_lmdb_meta_env') or self._lmdb_meta_env is None:
735
+ try:
736
+ import lmdb
737
+ lmdb_path = str(self.storage_dir / "metadata.lmdb")
738
+ Path(lmdb_path).mkdir(parents=True, exist_ok=True)
739
+ self._lmdb_meta_env = lmdb.open(lmdb_path, map_size=64 * 1024 * 1024)
740
+ except Exception:
741
+ self._lmdb_meta_env = None
742
+ return self._lmdb_meta_env
743
+
744
+ def _save_metadata_lmdb(self):
745
+ """Persist all metadata to LMDB, deleting entries for removed graphs."""
746
+ env = self._get_lmdb_meta_env()
747
+ if not env:
748
+ return
749
+ with env.begin(write=True) as txn:
750
+ # Collect existing LMDB keys so we can delete orphaned ones
751
+ existing_keys = {key.decode() for key, _ in txn.cursor()}
752
+ for name, metadata in self.metadata.items():
753
+ meta_dict = asdict(metadata)
754
+ meta_dict['created_at'] = metadata.created_at.isoformat()
755
+ meta_dict['updated_at'] = metadata.updated_at.isoformat()
756
+ if metadata.last_accessed:
757
+ meta_dict['last_accessed'] = metadata.last_accessed.isoformat()
758
+ txn.put(name.encode(), json.dumps(meta_dict).encode())
759
+ existing_keys.discard(name)
760
+ # Delete LMDB entries for graphs that no longer exist
761
+ for orphaned_key in existing_keys:
762
+ txn.delete(orphaned_key.encode())
763
+ logger.debug("[REGISTRY] Deleted orphaned LMDB entry: %s", orphaned_key)
764
+
765
+ def _load_metadata_lmdb(self) -> bool:
766
+ """Try loading metadata from LMDB. Returns True if successful."""
767
+ env = self._get_lmdb_meta_env()
768
+ if not env:
769
+ return False
770
+ try:
771
+ with env.begin() as txn:
772
+ cursor = txn.cursor()
773
+ count = 0
774
+ for key, val in cursor:
775
+ name = key.decode()
776
+ meta_data = json.loads(val.decode())
777
+ meta_data['created_at'] = datetime.fromisoformat(meta_data['created_at'])
778
+ meta_data['updated_at'] = datetime.fromisoformat(meta_data['updated_at'])
779
+ if meta_data.get('last_accessed'):
780
+ meta_data['last_accessed'] = datetime.fromisoformat(meta_data['last_accessed'])
781
+ self.metadata[name] = GraphMetadata(**meta_data)
782
+ count += 1
783
+ if count > 0:
784
+ logger.info("[REGISTRY] Loaded %d graph metadata from LMDB", count)
785
+ return True
786
+ except Exception as e:
787
+ logger.debug("[REGISTRY] LMDB metadata load failed: %s", e)
788
+ return False
789
+
790
+ def _auto_cleanup(self):
791
+ """Evict LRU in-memory graphs when over max_graphs limit.
792
+
793
+ Called internally after loading/creating graphs. Assumes the caller
794
+ already holds ``self._lock``.
795
+ """
796
+ max_graphs = self.cache_config['max_graphs']
797
+ if len(self.graphs) <= max_graphs:
798
+ return
799
+
800
+ # Sort loaded graphs by last_accessed (oldest first)
801
+ sorted_graphs = sorted(
802
+ [(name, self.metadata[name].last_accessed or self.metadata[name].created_at)
803
+ for name in self.graphs
804
+ if name in self.metadata],
805
+ key=lambda x: x[1]
806
+ )
807
+
808
+ excess = len(self.graphs) - max_graphs
809
+ for name, _ in sorted_graphs[:excess]:
810
+ # Save to disk before evicting so in-memory changes aren't lost
811
+ try:
812
+ self.save_graph(name, create_checkpoint=False)
813
+ except Exception:
814
+ logger.warning(f"[REGISTRY] Failed to save graph '{name}' before eviction")
815
+ logger.info(f"[REGISTRY] Auto-evicting LRU graph '{name}' from memory")
816
+ del self.graphs[name]
817
+
818
+ def cleanup_cache(self):
819
+ """Clean up cache based on configuration (public, acquires lock)."""
820
+ with self._lock:
821
+ # Remove graphs that haven't been accessed recently
822
+ cutoff_time = datetime.now() - timedelta(hours=self.cache_config['ttl_hours'])
823
+
824
+ graphs_to_remove = []
825
+ for name, metadata in self.metadata.items():
826
+ if (name in self.graphs and
827
+ metadata.last_accessed and
828
+ metadata.last_accessed < cutoff_time):
829
+ graphs_to_remove.append(name)
830
+
831
+ for name in graphs_to_remove:
832
+ if name in self.graphs:
833
+ del self.graphs[name]
834
+
835
+ # Delegate count/size eviction to _auto_cleanup
836
+ self._auto_cleanup()
837
+
838
+ # Check total size
839
+ total_size = sum(metadata.size_mb for metadata in self.metadata.values())
840
+ if total_size > self.cache_config['max_size_mb']:
841
+ sorted_by_size = sorted(
842
+ [(name, metadata.size_mb) for name, metadata in self.metadata.items()],
843
+ key=lambda x: x[1],
844
+ reverse=True
845
+ )
846
+
847
+ current_size = total_size
848
+ for name, size in sorted_by_size:
849
+ if current_size <= self.cache_config['max_size_mb']:
850
+ break
851
+ if name in self.graphs:
852
+ del self.graphs[name]
853
+ current_size -= size
854
+
855
+ def get_stats(self) -> Dict[str, Any]:
856
+ """Get registry statistics."""
857
+ return {
858
+ 'total_graphs': len(self.metadata),
859
+ 'loaded_graphs': len(self.graphs),
860
+ 'total_nodes': sum(metadata.num_nodes for metadata in self.metadata.values()),
861
+ 'total_edges': sum(metadata.num_edges for metadata in self.metadata.values()),
862
+ 'total_size_mb': sum(metadata.size_mb for metadata in self.metadata.values()),
863
+ 'cache_config': self.cache_config
864
+ }
865
+
866
+ def search_graphs(self, query: str, tags: List[str] = None) -> List[Dict[str, Any]]:
867
+ """
868
+ Search graphs by name, tags, or metadata.
869
+
870
+ Args:
871
+ query: Search query
872
+ tags: Optional tags to filter by
873
+
874
+ Returns:
875
+ List of matching graph metadata
876
+ """
877
+ results = []
878
+ query_lower = query.lower()
879
+
880
+ for name, metadata in self.metadata.items():
881
+ # Check name match
882
+ if query_lower in name.lower():
883
+ results.append(name)
884
+ continue
885
+
886
+ # Check tag match
887
+ if tags:
888
+ if any(tag in metadata.tags for tag in tags):
889
+ results.append(name)
890
+ continue
891
+
892
+ # Check metadata match
893
+ if (query_lower in str(metadata.num_nodes) or
894
+ query_lower in str(metadata.num_edges) or
895
+ query_lower in metadata.embedding_model.lower() if metadata.embedding_model else False):
896
+ results.append(name)
897
+
898
+ # Return full metadata for matches
899
+ return [asdict(self.metadata[name]) for name in results]
900
+
901
+ # ── Property Indexes ─────────────────────────────────────────────────
902
+ # Lightweight in-memory indexes built incrementally on add_node calls.
903
+ # Structure: {graph_name: {field: {value: set(node_ids)}}}
904
+ _INDEXED_FIELDS = ("node_type", "_sentiment", "stock_symbol", "portfolio_id", "source_type")
905
+
906
+ def _ensure_property_index(self, graph_name: str) -> Dict[str, Dict[str, set]]:
907
+ """Get or create the property index dict for a graph."""
908
+ if not hasattr(self, '_property_indexes'):
909
+ self._property_indexes: Dict[str, Dict[str, Dict[str, set]]] = {}
910
+ if graph_name not in self._property_indexes:
911
+ self._property_indexes[graph_name] = {f: {} for f in self._INDEXED_FIELDS}
912
+ return self._property_indexes[graph_name]
913
+
914
+ def index_node(self, graph_name: str, node_id: str, label: str = "", properties: Dict = None):
915
+ """Index a node's key properties for O(1) lookup.
916
+
917
+ Called incrementally when nodes are added — not a retroactive rebuild.
918
+ """
919
+ idx = self._ensure_property_index(graph_name)
920
+ # Index by node_type / label
921
+ if label:
922
+ idx["node_type"].setdefault(label, set()).add(node_id)
923
+ if properties:
924
+ for field in self._INDEXED_FIELDS:
925
+ if field == "node_type":
926
+ continue # handled above via label
927
+ val = properties.get(field)
928
+ if val is not None:
929
+ idx[field].setdefault(str(val), set()).add(node_id)
930
+
931
+ def query_nodes(self, graph_name: str, node_type: str = None,
932
+ properties: Dict = None, limit: int = 100) -> List:
933
+ """Query nodes with indexed lookup instead of full scan.
934
+
935
+ Uses indexes for: node_type, _sentiment, stock_symbol, portfolio_id, source_type.
936
+ Falls back to scan if no index exists for the requested fields.
937
+ """
938
+ graph = self.get_graph(graph_name, load_if_missing=True)
939
+ if not graph:
940
+ return []
941
+
942
+ # Try indexed path first
943
+ idx = self._ensure_property_index(graph_name)
944
+ candidate_ids = None
945
+
946
+ # Narrow by node_type index
947
+ if node_type and "node_type" in idx and node_type in idx["node_type"]:
948
+ candidate_ids = set(idx["node_type"][node_type])
949
+
950
+ # Narrow by property indexes
951
+ if properties:
952
+ for field, val in properties.items():
953
+ if field in idx and str(val) in idx[field]:
954
+ matching = idx[field][str(val)]
955
+ if candidate_ids is None:
956
+ candidate_ids = set(matching)
957
+ else:
958
+ candidate_ids &= matching
959
+
960
+ # If we have indexed candidates, resolve to nodes
961
+ if candidate_ids is not None:
962
+ results = []
963
+ adapter = getattr(graph, 'csr_adapter', graph)
964
+ csr = getattr(adapter, 'csr_storage', None)
965
+ for nid in list(candidate_ids)[:limit]:
966
+ node = None
967
+ if csr and hasattr(csr, 'get_node'):
968
+ node = csr.get_node(nid)
969
+ elif hasattr(graph, 'node_index') and nid in graph.node_index:
970
+ node = graph.node_index[nid]
971
+ if node:
972
+ results.append(node)
973
+ return results
974
+
975
+ # Fallback: use get_nodes_by_type if only node_type is requested
976
+ if node_type and not properties:
977
+ return self.get_nodes_by_type(graph_name, node_type, limit=limit)
978
+
979
+ # Final fallback: full scan with filter
980
+ return self._scan_nodes(graph, node_type=node_type, properties=properties, limit=limit)
981
+
982
+ def get_nodes_by_type(self, graph_name: str, node_type: str, limit: int = 100) -> List:
983
+ """Get nodes by type using CSR adapter's type storage if available,
984
+ falling back to filtered scan."""
985
+ graph = self.get_graph(graph_name, load_if_missing=True)
986
+ if not graph:
987
+ return []
988
+
989
+ # Try CSR adapter's typed storage
990
+ adapter = getattr(graph, 'csr_adapter', graph)
991
+ csr = getattr(adapter, 'csr_storage', None)
992
+ if csr and hasattr(csr, 'get_all_nodes'):
993
+ try:
994
+ nodes = csr.get_all_nodes(node_type=node_type)
995
+ return nodes[:limit]
996
+ except TypeError:
997
+ # get_all_nodes may not accept node_type kwarg
998
+ pass
999
+
1000
+ # Fallback: property index
1001
+ idx = self._ensure_property_index(graph_name)
1002
+ if node_type in idx.get("node_type", {}):
1003
+ ids = list(idx["node_type"][node_type])[:limit]
1004
+ results = []
1005
+ for nid in ids:
1006
+ if hasattr(graph, 'node_index') and nid in graph.node_index:
1007
+ results.append(graph.node_index[nid])
1008
+ elif csr and hasattr(csr, 'get_node'):
1009
+ node = csr.get_node(nid)
1010
+ if node:
1011
+ results.append(node)
1012
+ return results
1013
+
1014
+ # Final fallback: scan
1015
+ return self._scan_nodes(graph, node_type=node_type, limit=limit)
1016
+
1017
+ def _scan_nodes(self, graph, node_type: str = None,
1018
+ properties: Dict = None, limit: int = 100) -> List:
1019
+ """Full scan with optional filters (last resort)."""
1020
+ results = []
1021
+ all_nodes = []
1022
+ if hasattr(graph, 'node_index') and graph.node_index:
1023
+ all_nodes = list(graph.node_index.values())
1024
+ else:
1025
+ adapter = getattr(graph, 'csr_adapter', graph)
1026
+ if hasattr(adapter, 'get_all_nodes'):
1027
+ all_nodes = adapter.get_all_nodes()
1028
+
1029
+ for node in all_nodes:
1030
+ if len(results) >= limit:
1031
+ break
1032
+ label = getattr(node, 'label', getattr(node, 'node_type', ''))
1033
+ if node_type and label != node_type:
1034
+ continue
1035
+ if properties:
1036
+ props = getattr(node, 'properties', {})
1037
+ if not all(props.get(k) == v for k, v in properties.items()):
1038
+ continue
1039
+ results.append(node)
1040
+ return results
1041
+
1042
+ # Global singleton instance for backward compatibility
1043
+ graph_registry = GraphRegistry()