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.
- contextsynapse/__init__.py +107 -0
- contextsynapse/__main__.py +9 -0
- contextsynapse/a2a/__init__.py +28 -0
- contextsynapse/a2a/client.py +120 -0
- contextsynapse/a2a/discovery.py +128 -0
- contextsynapse/a2a/handlers.py +164 -0
- contextsynapse/a2a/models.py +246 -0
- contextsynapse/a2a/streaming.py +92 -0
- contextsynapse/a2a/task_manager.py +275 -0
- contextsynapse/adapters/__init__.py +98 -0
- contextsynapse/adapters/_base.py +298 -0
- contextsynapse/adapters/autogen/__init__.py +4 -0
- contextsynapse/adapters/autogen/tools.py +34 -0
- contextsynapse/adapters/crewai/__init__.py +4 -0
- contextsynapse/adapters/crewai/tools.py +63 -0
- contextsynapse/adapters/langchain/__init__.py +19 -0
- contextsynapse/adapters/langchain/message_history.py +17 -0
- contextsynapse/adapters/langchain/retriever.py +158 -0
- contextsynapse/adapters/langchain/tools.py +43 -0
- contextsynapse/adapters/langgraph/__init__.py +21 -0
- contextsynapse/adapters/langgraph/checkpoint.py +223 -0
- contextsynapse/adapters/langgraph/context_tools.py +341 -0
- contextsynapse/adapters/langgraph/message_history.py +138 -0
- contextsynapse/adapters/langgraph/tools.py +43 -0
- contextsynapse/adapters/llamaindex/__init__.py +4 -0
- contextsynapse/adapters/llamaindex/tools.py +64 -0
- contextsynapse/adapters/openai/__init__.py +24 -0
- contextsynapse/adapters/openai/tools.py +206 -0
- contextsynapse/adapters/pydantic_ai/__init__.py +4 -0
- contextsynapse/adapters/pydantic_ai/tools.py +36 -0
- contextsynapse/adapters/swarm/__init__.py +4 -0
- contextsynapse/adapters/swarm/tools.py +44 -0
- contextsynapse/agents/__init__.py +6 -0
- contextsynapse/agents/auto_capture.py +80 -0
- contextsynapse/agents/dispatcher.py +299 -0
- contextsynapse/agents/notifications.py +112 -0
- contextsynapse/aiql/__init__.py +71 -0
- contextsynapse/aiql/cache.py +341 -0
- contextsynapse/aiql/compiler/__init__.py +62 -0
- contextsynapse/aiql/compiler/optimizer.py +126 -0
- contextsynapse/aiql/compiler/planner.py +366 -0
- contextsynapse/aiql/compiler/statistics.py +235 -0
- contextsynapse/aiql/compiler/validator.py +425 -0
- contextsynapse/aiql/engine/__init__.py +11 -0
- contextsynapse/aiql/engine/chunk_storage.py +2584 -0
- contextsynapse/aiql/engine/executor.py +7737 -0
- contextsynapse/aiql/engine/extraction_integration.py +442 -0
- contextsynapse/aiql/engine/graph_builder.py +405 -0
- contextsynapse/aiql/engine/node_creation_from_normalized.py +691 -0
- contextsynapse/aiql/engine/performance_monitor.py +289 -0
- contextsynapse/aiql/engine/postprocessing.py +365 -0
- contextsynapse/aiql/engine/ray_stage_executors.py +918 -0
- contextsynapse/aiql/engine/redis_cache.py +106 -0
- contextsynapse/aiql/engine/streaming_json.py +366 -0
- contextsynapse/aiql/engine/streaming_parquet.py +568 -0
- contextsynapse/aiql/engine/streaming_ray_chunking.py +419 -0
- contextsynapse/aiql/grammar/__init__.py +14 -0
- contextsynapse/aiql/grammar/aiql_grammar.py +1822 -0
- contextsynapse/aiql/grammar/base.py +71 -0
- contextsynapse/aiql/parser/__init__.py +46 -0
- contextsynapse/aiql/parser/aiql_parser.py +11330 -0
- contextsynapse/aiql/parser/base_parser.py +47 -0
- contextsynapse/api/__init__.py +5 -0
- contextsynapse/api/a2a_router.py +233 -0
- contextsynapse/api/admin_router.py +140 -0
- contextsynapse/api/agent_worker_router.py +978 -0
- contextsynapse/api/algorithms_router.py +317 -0
- contextsynapse/api/api.py +4144 -0
- contextsynapse/api/audit.py +204 -0
- contextsynapse/api/auth.py +441 -0
- contextsynapse/api/auth_router.py +537 -0
- contextsynapse/api/billing.py +261 -0
- contextsynapse/api/billing_router.py +275 -0
- contextsynapse/api/boundary_router.py +452 -0
- contextsynapse/api/cognition_router.py +116 -0
- contextsynapse/api/connector_router.py +237 -0
- contextsynapse/api/context_router.py +1619 -0
- contextsynapse/api/dashboard_router.py +11367 -0
- contextsynapse/api/errors.py +183 -0
- contextsynapse/api/events.py +88 -0
- contextsynapse/api/experiment_bootstrap.py +321 -0
- contextsynapse/api/experiment_orchestrator.py +1078 -0
- contextsynapse/api/experiment_router.py +2089 -0
- contextsynapse/api/feature_gate.py +89 -0
- contextsynapse/api/federation_router.py +108 -0
- contextsynapse/api/integrations.py +567 -0
- contextsynapse/api/integrations_router.py +461 -0
- contextsynapse/api/intelligence_router.py +1028 -0
- contextsynapse/api/metering.py +155 -0
- contextsynapse/api/metrics.py +112 -0
- contextsynapse/api/models.py +38 -0
- contextsynapse/api/monitoring_router.py +174 -0
- contextsynapse/api/pipeline_router.py +1437 -0
- contextsynapse/api/playground_conn.py +116 -0
- contextsynapse/api/projects_router.py +131 -0
- contextsynapse/api/queries_router.py +282 -0
- contextsynapse/api/quota.py +95 -0
- contextsynapse/api/rate_limit.py +64 -0
- contextsynapse/api/search_router.py +261 -0
- contextsynapse/api/shield_router.py +447 -0
- contextsynapse/api/tenant_guard.py +103 -0
- contextsynapse/api/tenant_quotas.py +231 -0
- contextsynapse/api/tenants.py +305 -0
- contextsynapse/api/users.py +646 -0
- contextsynapse/api/v1.py +167 -0
- contextsynapse/api/vertical_builder_router.py +387 -0
- contextsynapse/benchmark/__init__.py +15 -0
- contextsynapse/benchmark/__main__.py +64 -0
- contextsynapse/benchmark/agents.py +338 -0
- contextsynapse/benchmark/live_ab.py +338 -0
- contextsynapse/benchmark/reporter.py +149 -0
- contextsynapse/benchmark/runner.py +264 -0
- contextsynapse/benchmark/savings.py +266 -0
- contextsynapse/benchmark/token_counter.py +212 -0
- contextsynapse/benchmark/workloads.py +289 -0
- contextsynapse/buffer/__init__.py +154 -0
- contextsynapse/buffer/base.py +257 -0
- contextsynapse/buffer/duckdb_buffer.py +246 -0
- contextsynapse/buffer/file_buffer.py +229 -0
- contextsynapse/buffer/lmdb_buffer.py +286 -0
- contextsynapse/buffer/local_buffer.py +154 -0
- contextsynapse/buffer/redis_buffer.py +211 -0
- contextsynapse/buffer/sqlite_buffer.py +266 -0
- contextsynapse/cache.py +2 -0
- contextsynapse/cli/__init__.py +40 -0
- contextsynapse/cli/cli.py +475 -0
- contextsynapse/cli/config_cli.py +177 -0
- contextsynapse/cli/scaffold.py +465 -0
- contextsynapse/cognition/__init__.py +31 -0
- contextsynapse/cognition/derivation.py +55 -0
- contextsynapse/cognition/events.py +135 -0
- contextsynapse/cognition/feedback.py +52 -0
- contextsynapse/cognition/hallucination.py +90 -0
- contextsynapse/cognition/invalidation.py +70 -0
- contextsynapse/cognition/read_tracker.py +50 -0
- contextsynapse/collaboration.py +2 -0
- contextsynapse/comparison.py +2 -0
- contextsynapse/config/__init__.py +10 -0
- contextsynapse/config/connectors/amfi_nav.yaml +18 -0
- contextsynapse/config/connectors/auto_sector.yaml +20 -0
- contextsynapse/config/connectors/banking_sector.yaml +20 -0
- contextsynapse/config/connectors/energy_sector.yaml +20 -0
- contextsynapse/config/connectors/fmcg_sector.yaml +20 -0
- contextsynapse/config/connectors/nsdl_fpi.yaml +9 -0
- contextsynapse/config/connectors/nse_price.yaml +61 -0
- contextsynapse/config/connectors/pharma_sector.yaml +20 -0
- contextsynapse/config/connectors/rbi_dbie.yaml +12 -0
- contextsynapse/config/context_presets/ai_research.json +25 -0
- contextsynapse/config/context_presets/chatgpt_conversations.json +19 -0
- contextsynapse/config/context_presets/claude_conversations.json +19 -0
- contextsynapse/config/context_presets/legal_compliance.json +21 -0
- contextsynapse/config/context_presets/meeting_notes.json +21 -0
- contextsynapse/config/context_presets/slack_engineering.json +21 -0
- contextsynapse/config/context_presets/tech_climate.json +24 -0
- contextsynapse/config/context_presets/toi_politics.json +25 -0
- contextsynapse/config/database_config.py +453 -0
- contextsynapse/config/deployment.py +221 -0
- contextsynapse/config/gpt_action_schema.json +164 -0
- contextsynapse/config/indicators/india_macro.yaml +213 -0
- contextsynapse/config/logging_config.py +88 -0
- contextsynapse/config/mode_config.py +223 -0
- contextsynapse/config/schemas/api_spec.yaml +48 -0
- contextsynapse/config/schemas/architecture_doc.yaml +51 -0
- contextsynapse/config/schemas/chat_history.yaml +90 -0
- contextsynapse/config/schemas/chatgpt_conversation.yaml +101 -0
- contextsynapse/config/schemas/claude_conversation.yaml +148 -0
- contextsynapse/config/schemas/contract.yaml +48 -0
- contextsynapse/config/schemas/conversation.yaml +167 -0
- contextsynapse/config/schemas/database.yaml +74 -0
- contextsynapse/config/schemas/decision.yaml +72 -0
- contextsynapse/config/schemas/ecommerce.yaml +56 -0
- contextsynapse/config/schemas/education.yaml +42 -0
- contextsynapse/config/schemas/healthcare.yaml +133 -0
- contextsynapse/config/schemas/hr_document.yaml +48 -0
- contextsynapse/config/schemas/invoice.yaml +40 -0
- contextsynapse/config/schemas/knowledge_base.yaml +75 -0
- contextsynapse/config/schemas/legal.yaml +42 -0
- contextsynapse/config/schemas/marketing_content.yaml +48 -0
- contextsynapse/config/schemas/meeting_notes.yaml +51 -0
- contextsynapse/config/schemas/news_article.yaml +61 -0
- contextsynapse/config/schemas/real_estate.yaml +49 -0
- contextsynapse/config/schemas/requirements_doc.yaml +48 -0
- contextsynapse/config/schemas/research_paper.yaml +56 -0
- contextsynapse/config/schemas/resume.yaml +49 -0
- contextsynapse/config/schemas/retail.yaml +42 -0
- contextsynapse/config/schemas/rule_meta.yaml +173 -0
- contextsynapse/config/schemas/rules.yaml +72 -0
- contextsynapse/config/schemas/screener_meta.yaml +236 -0
- contextsynapse/config/schemas/sdlc.yaml +103 -0
- contextsynapse/config/schemas/session_graph.yaml +172 -0
- contextsynapse/config/schemas/support_ticket.yaml +48 -0
- contextsynapse/config/schemas/web.yaml +64 -0
- contextsynapse/config/sensors/earnings_calendar.yaml +52 -0
- contextsynapse/config/sensors/global_risk.yaml +53 -0
- contextsynapse/config/sensors/government_policy.yaml +54 -0
- contextsynapse/config/sensors/supply_chain.yaml +50 -0
- contextsynapse/config/sensors/templates/behavioral.yaml +56 -0
- contextsynapse/config/sensors/templates/equity_sentiment.yaml +48 -0
- contextsynapse/config/sensors/templates/global_macro.yaml +47 -0
- contextsynapse/config/sensors/templates/market_data.yaml +118 -0
- contextsynapse/config/sensors/templates/sector_technical.yaml +47 -0
- contextsynapse/config/sensors/weather.yaml +52 -0
- contextsynapse/config/storage_strategy.py +202 -0
- contextsynapse/config/templates/competitive_intel.yaml +39 -0
- contextsynapse/config/templates/geopolitical_risk.yaml +46 -0
- contextsynapse/config/templates/market_analysis.yaml +58 -0
- contextsynapse/connectors/__init__.py +15 -0
- contextsynapse/connectors/api_router.py +121 -0
- contextsynapse/connectors/base.py +357 -0
- contextsynapse/connectors/financial/audio.py +170 -0
- contextsynapse/connectors/financial/youtube.py +272 -0
- contextsynapse/connectors/github.py +164 -0
- contextsynapse/connectors/jira.py +120 -0
- contextsynapse/connectors/kafka_connector.py +124 -0
- contextsynapse/connectors/pipeline_connector.py +747 -0
- contextsynapse/connectors/registry.py +116 -0
- contextsynapse/connectors/scheduler.py +87 -0
- contextsynapse/connectors/universal.py +360 -0
- contextsynapse/context/__init__.py +96 -0
- contextsynapse/context/__main__.py +4 -0
- contextsynapse/context/acl.py +125 -0
- contextsynapse/context/agent_card.py +201 -0
- contextsynapse/context/agent_memory.py +814 -0
- contextsynapse/context/agents.py +472 -0
- contextsynapse/context/agents_redis.py +391 -0
- contextsynapse/context/assembled.py +204 -0
- contextsynapse/context/attribution.py +113 -0
- contextsynapse/context/blob.py +184 -0
- contextsynapse/context/boundaries.py +187 -0
- contextsynapse/context/boundary.py +507 -0
- contextsynapse/context/bundle.py +155 -0
- contextsynapse/context/cli.py +519 -0
- contextsynapse/context/collaboration.py +165 -0
- contextsynapse/context/compiler.py +604 -0
- contextsynapse/context/composite.py +536 -0
- contextsynapse/context/compression.py +147 -0
- contextsynapse/context/context_manager.py +888 -0
- contextsynapse/context/context_schema.py +90 -0
- contextsynapse/context/context_schema.yaml +105 -0
- contextsynapse/context/context_units.py +891 -0
- contextsynapse/context/conversation.py +488 -0
- contextsynapse/context/dedup.py +207 -0
- contextsynapse/context/document_processor.py +698 -0
- contextsynapse/context/embedding_hooks.py +338 -0
- contextsynapse/context/execution_schema.py +274 -0
- contextsynapse/context/fan_out.py +138 -0
- contextsynapse/context/frozen.py +80 -0
- contextsynapse/context/gravity.py +227 -0
- contextsynapse/context/hub.py +1401 -0
- contextsynapse/context/ingest.py +391 -0
- contextsynapse/context/injection.py +226 -0
- contextsynapse/context/interaction_graphifier.py +562 -0
- contextsynapse/context/layers.py +574 -0
- contextsynapse/context/projection.py +1195 -0
- contextsynapse/context/promotion.py +634 -0
- contextsynapse/context/proof.py +131 -0
- contextsynapse/context/propagation.py +577 -0
- contextsynapse/context/pubsub.py +135 -0
- contextsynapse/context/quality.py +1054 -0
- contextsynapse/context/quality_tracker.py +270 -0
- contextsynapse/context/scoping.py +370 -0
- contextsynapse/context/seed.py +992 -0
- contextsynapse/context/session.py +1142 -0
- contextsynapse/context/session_graph.py +593 -0
- contextsynapse/context/session_memory.py +309 -0
- contextsynapse/context/session_resolver.py +111 -0
- contextsynapse/context/skill_learning.py +237 -0
- contextsynapse/context/smart_budget.py +129 -0
- contextsynapse/context/source_policy.py +242 -0
- contextsynapse/context/store_factory.py +53 -0
- contextsynapse/context/sync.py +648 -0
- contextsynapse/context/token.py +236 -0
- contextsynapse/context/vector_integration.py +297 -0
- contextsynapse/context/webhooks.py +194 -0
- contextsynapse/context/working_memory.py +224 -0
- contextsynapse/core/__init__.py +18 -0
- contextsynapse/core/archiver.py +237 -0
- contextsynapse/core/change_stream.py +282 -0
- contextsynapse/core/checkpoint.py +310 -0
- contextsynapse/core/cloud_storage.py +387 -0
- contextsynapse/core/context_state.py +651 -0
- contextsynapse/core/cron.py +324 -0
- contextsynapse/core/datasource.py +643 -0
- contextsynapse/core/db.py +289 -0
- contextsynapse/core/federation.py +283 -0
- contextsynapse/core/graph_coordinator.py +201 -0
- contextsynapse/core/graph_intelligence.py +388 -0
- contextsynapse/core/graph_structures.py +106 -0
- contextsynapse/core/graph_sync.py +157 -0
- contextsynapse/core/hybrid_graph_storage.py +2998 -0
- contextsynapse/core/lifecycle.py +691 -0
- contextsynapse/core/multiworker.py +378 -0
- contextsynapse/core/pruning.py +312 -0
- contextsynapse/core/redis_registry.py +524 -0
- contextsynapse/core/registry.py +1043 -0
- contextsynapse/core/registry_factory.py +51 -0
- contextsynapse/core/registry_metadata.py +294 -0
- contextsynapse/core/replay.py +354 -0
- contextsynapse/core/replication.py +339 -0
- contextsynapse/core/time_travel.py +260 -0
- contextsynapse/core/write_behind.py +152 -0
- contextsynapse/core/write_context.py +39 -0
- contextsynapse/db/__init__.py +1 -0
- contextsynapse/db/postgres.py +267 -0
- contextsynapse/db/profile.py +236 -0
- contextsynapse/db/rules.py +328 -0
- contextsynapse/demo/__init__.py +0 -0
- contextsynapse/demo/report.py +147 -0
- contextsynapse/demo/runner.py +520 -0
- contextsynapse/demo/scenario.py +165 -0
- contextsynapse/demo/session_demo.py +280 -0
- contextsynapse/demo/session_report.py +143 -0
- contextsynapse/demo/universal_runner.py +257 -0
- contextsynapse/engine/__init__.py +15 -0
- contextsynapse/engine/core.py +555 -0
- contextsynapse/extraction/__init__.py +69 -0
- contextsynapse/extraction/cli.py +274 -0
- contextsynapse/extraction/config.py +107 -0
- contextsynapse/extraction/examples/example_usage.py +118 -0
- contextsynapse/extraction/examples/sample_manifest.json +53 -0
- contextsynapse/extraction/examples/sample_normalized.json +185 -0
- contextsynapse/extraction/extract_engine.py +440 -0
- contextsynapse/extraction/extractors/__init__.py +37 -0
- contextsynapse/extraction/extractors/chat_export_extractor.py +189 -0
- contextsynapse/extraction/extractors/csv_extractor.py +83 -0
- contextsynapse/extraction/extractors/docx_extractor.py +129 -0
- contextsynapse/extraction/extractors/excel_extractor.py +152 -0
- contextsynapse/extraction/extractors/html_extractor.py +146 -0
- contextsynapse/extraction/extractors/pdf_extractor.py +635 -0
- contextsynapse/extraction/extractors/pdf_extractor_layoutparser.py +408 -0
- contextsynapse/extraction/extractors/register_extractors.py +63 -0
- contextsynapse/extraction/extractors/text_extractor.py +65 -0
- contextsynapse/extraction/extractors/txt_extractor.py +120 -0
- contextsynapse/extraction/extractors/website_extractor.py +246 -0
- contextsynapse/extraction/fact_extractor.py +236 -0
- contextsynapse/extraction/file_manager.py +353 -0
- contextsynapse/extraction/hierarchy.py +239 -0
- contextsynapse/extraction/id_generator.py +450 -0
- contextsynapse/extraction/layout_detector.py +409 -0
- contextsynapse/extraction/llm_entity_extractor.py +301 -0
- contextsynapse/extraction/normalized_store.py +480 -0
- contextsynapse/extraction/normalizer.py +594 -0
- contextsynapse/extraction/parquet_writer.py +598 -0
- contextsynapse/extraction/post_processors/__init__.py +40 -0
- contextsynapse/extraction/post_processors/column_reorganizer.py +507 -0
- contextsynapse/extraction/ray_normalization.py +233 -0
- contextsynapse/extraction/ray_runner.py +207 -0
- contextsynapse/extraction/redis_schema_registry.py +222 -0
- contextsynapse/extraction/registry.py +204 -0
- contextsynapse/extraction/schema_loader.py +663 -0
- contextsynapse/extraction/schema_registry.py +230 -0
- contextsynapse/extraction/semantic_normalizer.py +245 -0
- contextsynapse/extraction/version_manager.py +314 -0
- contextsynapse/gateway/__init__.py +24 -0
- contextsynapse/gateway/action_emitter.py +212 -0
- contextsynapse/gateway/cost_tracker.py +508 -0
- contextsynapse/gateway/gateway.py +377 -0
- contextsynapse/gateway/policy.py +303 -0
- contextsynapse/governance/__init__.py +13 -0
- contextsynapse/governance/layer.py +513 -0
- contextsynapse/ingestion/__init__.py +15 -0
- contextsynapse/ingestion/amplifier.py +288 -0
- contextsynapse/ingestion/async_ingest.py +463 -0
- contextsynapse/ingestion/chunker.py +303 -0
- contextsynapse/ingestion/chunking.py +58 -0
- contextsynapse/ingestion/cleaner.py +190 -0
- contextsynapse/ingestion/cleanse.py +252 -0
- contextsynapse/ingestion/connectors/__init__.py +38 -0
- contextsynapse/ingestion/connectors/base.py +300 -0
- contextsynapse/ingestion/connectors/chatgpt_memory.py +247 -0
- contextsynapse/ingestion/connectors/claude_memory.py +274 -0
- contextsynapse/ingestion/connectors/sap_ingestor.py +208 -0
- contextsynapse/ingestion/content_detector.py +80 -0
- contextsynapse/ingestion/content_monitor.py +502 -0
- contextsynapse/ingestion/dedup.py +106 -0
- contextsynapse/ingestion/feed.py +640 -0
- contextsynapse/ingestion/filters.py +461 -0
- contextsynapse/ingestion/graph_builder.py +705 -0
- contextsynapse/ingestion/input_classifier.py +307 -0
- contextsynapse/ingestion/job_manager.py +571 -0
- contextsynapse/ingestion/llm_extractor.py +610 -0
- contextsynapse/ingestion/orchestrator.py +116 -0
- contextsynapse/ingestion/parsers/__init__.py +19 -0
- contextsynapse/ingestion/parsers/graph_parser_registry.py +103 -0
- contextsynapse/ingestion/parsers/graphml_parser.py +141 -0
- contextsynapse/ingestion/parsers/jsonld_parser.py +171 -0
- contextsynapse/ingestion/parsers/rdf_parser.py +150 -0
- contextsynapse/ingestion/pipeline_context.py +228 -0
- contextsynapse/ingestion/pipeline_store.py +1195 -0
- contextsynapse/ingestion/queue.py +133 -0
- contextsynapse/ingestion/reprocess.py +179 -0
- contextsynapse/ingestion/scenario_router.py +224 -0
- contextsynapse/ingestion/schema_extractor.py +759 -0
- contextsynapse/ingestion/schema_validator.py +274 -0
- contextsynapse/ingestion/sdlc_ingest.py +87 -0
- contextsynapse/ingestion/smart_ingest.py +2451 -0
- contextsynapse/ingestion/source_connector.py +171 -0
- contextsynapse/ingestion/stage_executor.py +2095 -0
- contextsynapse/ingestion/stages/__init__.py +9 -0
- contextsynapse/ingestion/stages/tabular_to_graph.py +289 -0
- contextsynapse/ingestion/strategies.py +386 -0
- contextsynapse/ingestion/universal/__init__.py +4 -0
- contextsynapse/ingestion/universal/_operator_registry.py +59 -0
- contextsynapse/ingestion/universal/ingest_content.py +32 -0
- contextsynapse/ingestion/universal/operators/__init__.py +1 -0
- contextsynapse/ingestion/universal/operators/base.py +19 -0
- contextsynapse/ingestion/universal/operators/build_edges.py +75 -0
- contextsynapse/ingestion/universal/operators/cluster_topics.py +397 -0
- contextsynapse/ingestion/universal/operators/deduplicate.py +45 -0
- contextsynapse/ingestion/universal/operators/detect_signals.py +90 -0
- contextsynapse/ingestion/universal/operators/embed.py +80 -0
- contextsynapse/ingestion/universal/operators/extract_entities.py +253 -0
- contextsynapse/ingestion/universal/operators/extract_preferences.py +117 -0
- contextsynapse/ingestion/universal/operators/filter_content.py +69 -0
- contextsynapse/ingestion/universal/operators/index_bm25.py +52 -0
- contextsynapse/ingestion/universal/operators/infer_domains.py +69 -0
- contextsynapse/ingestion/universal/operators/link_cross_reference.py +210 -0
- contextsynapse/ingestion/universal/operators/parse_records.py +66 -0
- contextsynapse/ingestion/universal/operators/resolve_entities.py +261 -0
- contextsynapse/ingestion/universal/operators/scanners/__init__.py +16 -0
- contextsynapse/ingestion/universal/operators/scanners/edges.py +173 -0
- contextsynapse/ingestion/universal/operators/scanners/git.py +187 -0
- contextsynapse/ingestion/universal/operators/scanners/github.py +135 -0
- contextsynapse/ingestion/universal/operators/scanners/github_api.py +509 -0
- contextsynapse/ingestion/universal/operators/scanners/helpers.py +67 -0
- contextsynapse/ingestion/universal/operators/scanners/jira.py +65 -0
- contextsynapse/ingestion/universal/operators/scanners/llm_enrichment.py +82 -0
- contextsynapse/ingestion/universal/operators/scanners/repo.py +149 -0
- contextsynapse/ingestion/universal/operators/sdlc_scan.py +213 -0
- contextsynapse/ingestion/universal/operators/spec_scanner.py +149 -0
- contextsynapse/ingestion/universal/operators/store_documents.py +72 -0
- contextsynapse/ingestion/universal/operators/synthesize_cu.py +180 -0
- contextsynapse/ingestion/universal/operators/validate_gate.py +65 -0
- contextsynapse/ingestion/universal/parsers/__init__.py +5 -0
- contextsynapse/ingestion/universal/parsers/record_parser.py +203 -0
- contextsynapse/ingestion/universal/parsers/turn_parser.py +329 -0
- contextsynapse/ingestion/universal/shared_signals.py +161 -0
- contextsynapse/ingestion/universal/stage_executor.py +219 -0
- contextsynapse/ingestion/web_crawler.py +265 -0
- contextsynapse/intelligence/__init__.py +53 -0
- contextsynapse/intelligence/alerts.py +198 -0
- contextsynapse/intelligence/collector.py +655 -0
- contextsynapse/intelligence/comparison.py +198 -0
- contextsynapse/intelligence/config.py +56 -0
- contextsynapse/intelligence/conflict_detector.py +182 -0
- contextsynapse/intelligence/context_gaps.py +192 -0
- contextsynapse/intelligence/context_radar.py +135 -0
- contextsynapse/intelligence/correlation_engine.py +416 -0
- contextsynapse/intelligence/correlation_template.py +120 -0
- contextsynapse/intelligence/dedup.py +296 -0
- contextsynapse/intelligence/event_bus.py +91 -0
- contextsynapse/intelligence/feedback_loop.py +101 -0
- contextsynapse/intelligence/freshness.py +307 -0
- contextsynapse/intelligence/fundamental_feed.py +551 -0
- contextsynapse/intelligence/geo_data.py +170 -0
- contextsynapse/intelligence/geo_reference.py +112 -0
- contextsynapse/intelligence/graph_fusion.py +465 -0
- contextsynapse/intelligence/heuristics.py +176 -0
- contextsynapse/intelligence/impact_tracker.py +336 -0
- contextsynapse/intelligence/macro_indicators.py +319 -0
- contextsynapse/intelligence/ocr.py +104 -0
- contextsynapse/intelligence/persistence.py +267 -0
- contextsynapse/intelligence/pipeline_health.py +130 -0
- contextsynapse/intelligence/price_feed.py +221 -0
- contextsynapse/intelligence/rate_limiter.py +130 -0
- contextsynapse/intelligence/reactive.py +411 -0
- contextsynapse/intelligence/runtime_context.py +390 -0
- contextsynapse/intelligence/sentiment_decay.py +426 -0
- contextsynapse/intelligence/session.py +277 -0
- contextsynapse/intelligence/signal_hierarchy.py +438 -0
- contextsynapse/intelligence/signals.py +185 -0
- contextsynapse/intelligence/source_watcher.py +132 -0
- contextsynapse/intelligence/table_enricher.py +256 -0
- contextsynapse/intelligence/tagger.py +319 -0
- contextsynapse/intelligence/timeseries.py +209 -0
- contextsynapse/intelligence/tracker.py +318 -0
- contextsynapse/intelligence/watchdog.py +392 -0
- contextsynapse/intelligence/watchdog_manager.py +263 -0
- contextsynapse/intelligence/webhook_receiver.py +165 -0
- contextsynapse/intelligence/weight_learner.py +234 -0
- contextsynapse/llm/__init__.py +8 -0
- contextsynapse/llm/client.py +348 -0
- contextsynapse/llm/rate_limiter.py +166 -0
- contextsynapse/logging_config.py +2 -0
- contextsynapse/marketplace/__init__.py +7 -0
- contextsynapse/marketplace/registry.py +173 -0
- contextsynapse/mcp/__init__.py +11 -0
- contextsynapse/mcp/__main__.py +4 -0
- contextsynapse/mcp/auth_middleware.py +212 -0
- contextsynapse/mcp/connection_pool.py +128 -0
- contextsynapse/mcp/server.py +986 -0
- contextsynapse/mcp/session_context.py +67 -0
- contextsynapse/memory/__init__.py +34 -0
- contextsynapse/memory/engine.py +475 -0
- contextsynapse/memory/temporal.py +327 -0
- contextsynapse/metadata/__init__.py +12 -0
- contextsynapse/metadata/metadata_db.py +272 -0
- contextsynapse/metadata/metadata_query_tool.py +300 -0
- contextsynapse/metadata/metadata_tracker.py +425 -0
- contextsynapse/models/__init__.py +27 -0
- contextsynapse/models/embedding_service.py +360 -0
- contextsynapse/models/model_registry.py +228 -0
- contextsynapse/multiworker.py +2 -0
- contextsynapse/pipelines/__init__.py +1 -0
- contextsynapse/pipelines/executor.py +851 -0
- contextsynapse/pipelines/filters.py +189 -0
- contextsynapse/pipelines/models.py +176 -0
- contextsynapse/pipelines/scheduler.py +268 -0
- contextsynapse/playground/__init__.py +1 -0
- contextsynapse/playground/templates.py +85 -0
- contextsynapse/plugins/__init__.py +43 -0
- contextsynapse/plugins/api.py +66 -0
- contextsynapse/plugins/base.py +350 -0
- contextsynapse/plugins/domain_interface.py +508 -0
- contextsynapse/plugins/domain_loader.py +241 -0
- contextsynapse/plugins/loader.py +148 -0
- contextsynapse/plugins/registry.py +265 -0
- contextsynapse/project/__init__.py +11 -0
- contextsynapse/project/code_context.py +807 -0
- contextsynapse/project/connectors/__init__.py +4 -0
- contextsynapse/project/connectors/github_connector.py +113 -0
- contextsynapse/project/connectors/jira_connector.py +160 -0
- contextsynapse/project/graph.py +1023 -0
- contextsynapse/project/project_context.py +545 -0
- contextsynapse/project/scanner_registry.py +61 -0
- contextsynapse/project/schema_manager.py +322 -0
- contextsynapse/project/schemas/knowledge.yaml +59 -0
- contextsynapse/project/schemas/sdlc.yaml +369 -0
- contextsynapse/project/sdlc_schema.py +172 -0
- contextsynapse/project/spec_parser.py +85 -0
- contextsynapse/project/task_lock.py +181 -0
- contextsynapse/project/task_stream.py +203 -0
- contextsynapse/project/team_tools.py +537 -0
- contextsynapse/py.typed +0 -0
- contextsynapse/realtime/__init__.py +6 -0
- contextsynapse/realtime/collaboration.py +147 -0
- contextsynapse/rules/__init__.py +10 -0
- contextsynapse/rules/engine.py +116 -0
- contextsynapse/rules/evaluators/__init__.py +6 -0
- contextsynapse/rules/evaluators/aggregate.py +205 -0
- contextsynapse/rules/evaluators/custom.py +170 -0
- contextsynapse/rules/evaluators/pre_action.py +125 -0
- contextsynapse/rules/evaluators/temporal.py +129 -0
- contextsynapse/rules/loader.py +52 -0
- contextsynapse/rules/models.py +124 -0
- contextsynapse/rules/universal.py +393 -0
- contextsynapse/scheduler.py +229 -0
- contextsynapse/schema/__init__.py +42 -0
- contextsynapse/schema/compiler.py +252 -0
- contextsynapse/schema/composer.py +158 -0
- contextsynapse/schema/dedup.py +73 -0
- contextsynapse/schema/derivation.py +172 -0
- contextsynapse/schema/sdl.py +326 -0
- contextsynapse/schema/validation_gate.py +124 -0
- contextsynapse/sdk/__init__.py +63 -0
- contextsynapse/sdk/agent.py +214 -0
- contextsynapse/sdk/client.py +269 -0
- contextsynapse/sdk/exceptions.py +23 -0
- contextsynapse/sdk/models.py +78 -0
- contextsynapse/sdk/session.py +408 -0
- contextsynapse/sdk/tracked.py +218 -0
- contextsynapse/sdk/worker.py +266 -0
- contextsynapse/sdk/workspace_client.py +98 -0
- contextsynapse/sdk/ws.py +41 -0
- contextsynapse/search/__init__.py +20 -0
- contextsynapse/search/embedding_cache.py +140 -0
- contextsynapse/search/enhanced_search.py +152 -0
- contextsynapse/search/fulltext.py +130 -0
- contextsynapse/search/graph_search.py +634 -0
- contextsynapse/search/lmdb_index.py +1218 -0
- contextsynapse/search/nl_to_aiql.py +199 -0
- contextsynapse/search/rag.py +1346 -0
- contextsynapse/search/rag_cache.py +167 -0
- contextsynapse/search/reasoning_chain.py +231 -0
- contextsynapse/search/redis_search.py +207 -0
- contextsynapse/search/retrieval_quality.py +314 -0
- contextsynapse/search/semantic_query.py +455 -0
- contextsynapse/search/text_resolver.py +159 -0
- contextsynapse/search/whoosh_search.py +373 -0
- contextsynapse/security/__init__.py +17 -0
- contextsynapse/security/audit.py +297 -0
- contextsynapse/security/audit_logger.py +97 -0
- contextsynapse/security/audit_trail.py +270 -0
- contextsynapse/security/auth_unified.py +284 -0
- contextsynapse/security/auto_tagger.py +230 -0
- contextsynapse/security/data_security.py +218 -0
- contextsynapse/security/encryption.py +207 -0
- contextsynapse/security/identity.py +447 -0
- contextsynapse/security/jwt_identity.py +59 -0
- contextsynapse/security/middleware.py +336 -0
- contextsynapse/security/pii.py +233 -0
- contextsynapse/security/rbac.py +96 -0
- contextsynapse/security/rls.py +205 -0
- contextsynapse/security/sanitize.py +233 -0
- contextsynapse/security/scoped_encryption.py +131 -0
- contextsynapse/security/tenant.py +401 -0
- contextsynapse/shield/__init__.py +21 -0
- contextsynapse/shield/anomaly.py +56 -0
- contextsynapse/shield/permissions.py +99 -0
- contextsynapse/shield/profile.py +103 -0
- contextsynapse/shield/shield.py +108 -0
- contextsynapse/shield/trust_engine.py +69 -0
- contextsynapse/skills/__init__.py +14 -0
- contextsynapse/skills/engine.py +233 -0
- contextsynapse/skills/loader.py +59 -0
- contextsynapse/skills/models.py +139 -0
- contextsynapse/storage/__init__.py +7 -0
- contextsynapse/storage/cache.py +208 -0
- contextsynapse/storage/columnar_store_v2.py +178 -0
- contextsynapse/storage/csr_graph_storage.py +587 -0
- contextsynapse/storage/csr_store.py +388 -0
- contextsynapse/storage/document_store.py +491 -0
- contextsynapse/storage/hnsw_index.py +294 -0
- contextsynapse/storage/hybrid_store_v2.py +278 -0
- contextsynapse/storage/lmdb_graph_storage.py +366 -0
- contextsynapse/storage/migration.py +279 -0
- contextsynapse/storage/namespace_store.py +346 -0
- contextsynapse/storage/pure_graph_storage.py +185 -0
- contextsynapse/storage/redis_graph_adapter.py +186 -0
- contextsynapse/storage/redis_graph_storage.py +375 -0
- contextsynapse/storage/router/__init__.py +29 -0
- contextsynapse/storage/router/duckdb_store.py +398 -0
- contextsynapse/storage/router/factory.py +83 -0
- contextsynapse/storage/router/interface.py +125 -0
- contextsynapse/storage/router/postgres_store.py +339 -0
- contextsynapse/storage/router/resolver.py +192 -0
- contextsynapse/storage/router/router.py +300 -0
- contextsynapse/storage/single_file_storage.py +391 -0
- contextsynapse/storage/storage_manager.py +214 -0
- contextsynapse/storage/storage_query_optimizer.py +314 -0
- contextsynapse/storage/wal.py +359 -0
- contextsynapse/temporal/__init__.py +0 -0
- contextsynapse/temporal/storage.py +106 -0
- contextsynapse/tests/__init__.py +11 -0
- contextsynapse/tests/regression/__init__.py +1 -0
- contextsynapse/tests/regression/add_metadata.py +395 -0
- contextsynapse/tests/regression/add_skip_flags.py +85 -0
- contextsynapse/tests/regression/cases/test_basic_create_node.yaml +20 -0
- contextsynapse/tests/regression/cases/test_basic_select.yaml +21 -0
- contextsynapse/tests/regression/cases/test_blockchain_audit_trail.yaml +37 -0
- contextsynapse/tests/regression/cases/test_blockchain_audit_trail_filtered.yaml +39 -0
- contextsynapse/tests/regression/cases/test_blockchain_blocks_range.yaml +39 -0
- contextsynapse/tests/regression/cases/test_blockchain_get_block.yaml +35 -0
- contextsynapse/tests/regression/cases/test_blockchain_latest_block.yaml +35 -0
- contextsynapse/tests/regression/cases/test_blockchain_length.yaml +37 -0
- contextsynapse/tests/regression/cases/test_blockchain_merkle_root.yaml +37 -0
- contextsynapse/tests/regression/cases/test_blockchain_verify.yaml +35 -0
- contextsynapse/tests/regression/cases/test_blockchain_verify_block.yaml +35 -0
- contextsynapse/tests/regression/cases/test_create_edge.yaml +28 -0
- contextsynapse/tests/regression/cases/test_delete_edge_basic.yaml +29 -0
- contextsynapse/tests/regression/cases/test_delete_node_basic.yaml +29 -0
- contextsynapse/tests/regression/cases/test_evaluation_mrr.yaml +40 -0
- contextsynapse/tests/regression/cases/test_evaluation_ndcg.yaml +40 -0
- contextsynapse/tests/regression/cases/test_evaluation_precision.yaml +40 -0
- contextsynapse/tests/regression/cases/test_evaluation_recall.yaml +40 -0
- contextsynapse/tests/regression/cases/test_group_by_basic.yaml +56 -0
- contextsynapse/tests/regression/cases/test_match_node_basic.yaml +29 -0
- contextsynapse/tests/regression/cases/test_match_node_with_order_limit.yaml +31 -0
- contextsynapse/tests/regression/cases/test_namespace_switch.yaml +18 -0
- contextsynapse/tests/regression/cases/test_pipeline_cascaded.yaml +61 -0
- contextsynapse/tests/regression/cases/test_pipeline_fixed_size_chunking.yaml +48 -0
- contextsynapse/tests/regression/cases/test_pipeline_gpt4_chunking.yaml +54 -0
- contextsynapse/tests/regression/cases/test_pipeline_hybrid_indexing.yaml +57 -0
- contextsynapse/tests/regression/cases/test_pipeline_large_embedding.yaml +52 -0
- contextsynapse/tests/regression/cases/test_pipeline_paragraph_chunking.yaml +49 -0
- contextsynapse/tests/regression/cases/test_pipeline_recursive_chunking.yaml +47 -0
- contextsynapse/tests/regression/cases/test_pipeline_run_pipeline.yaml +56 -0
- contextsynapse/tests/regression/cases/test_pipeline_section_based_chunking.yaml +53 -0
- contextsynapse/tests/regression/cases/test_pipeline_semantic_chunking.yaml +51 -0
- contextsynapse/tests/regression/cases/test_pipeline_with_entity_extraction.yaml +57 -0
- contextsynapse/tests/regression/cases/test_pipeline_with_relationships.yaml +62 -0
- contextsynapse/tests/regression/cases/test_rag_evaluation.yaml +38 -0
- contextsynapse/tests/regression/cases/test_rag_graph_bfs_with_eval.yaml +48 -0
- contextsynapse/tests/regression/cases/test_rag_graph_centrality.yaml +40 -0
- contextsynapse/tests/regression/cases/test_rag_graph_centrality_eval.yaml +35 -0
- contextsynapse/tests/regression/cases/test_rag_graph_community.yaml +40 -0
- contextsynapse/tests/regression/cases/test_rag_graph_community_eval.yaml +35 -0
- contextsynapse/tests/regression/cases/test_rag_graph_dfs_with_eval.yaml +35 -0
- contextsynapse/tests/regression/cases/test_rag_graph_multi_hop_eval.yaml +35 -0
- contextsynapse/tests/regression/cases/test_rag_graph_path.yaml +40 -0
- contextsynapse/tests/regression/cases/test_rag_graph_search_basic.yaml +38 -0
- contextsynapse/tests/regression/cases/test_rag_graph_search_bfs.yaml +52 -0
- contextsynapse/tests/regression/cases/test_rag_graph_search_dfs.yaml +41 -0
- contextsynapse/tests/regression/cases/test_rag_graph_search_multi_hop.yaml +43 -0
- contextsynapse/tests/regression/cases/test_rag_graph_search_shortest_path.yaml +41 -0
- contextsynapse/tests/regression/cases/test_rag_graph_search_weighted.yaml +43 -0
- contextsynapse/tests/regression/cases/test_rag_graph_shortest_path_eval.yaml +35 -0
- contextsynapse/tests/regression/cases/test_rag_graph_traversal.yaml +30 -0
- contextsynapse/tests/regression/cases/test_rag_graph_weighted_eval.yaml +35 -0
- contextsynapse/tests/regression/cases/test_rag_hybrid_search.yaml +33 -0
- contextsynapse/tests/regression/cases/test_rag_hybrid_with_prompt_embedding.yaml +44 -0
- contextsynapse/tests/regression/cases/test_rag_keyword_search.yaml +32 -0
- contextsynapse/tests/regression/cases/test_rag_multi_hop_graph.yaml +32 -0
- contextsynapse/tests/regression/cases/test_rag_path_based.yaml +34 -0
- contextsynapse/tests/regression/cases/test_rag_profile_based.yaml +32 -0
- contextsynapse/tests/regression/cases/test_rag_vector_search.yaml +32 -0
- contextsynapse/tests/regression/cases/test_rag_weighted_graph.yaml +34 -0
- contextsynapse/tests/regression/cases/test_rag_weighted_hybrid.yaml +31 -0
- contextsynapse/tests/regression/cases/test_semantic_boundary_chunking.yaml +57 -0
- contextsynapse/tests/regression/cases/test_semantic_boundary_detection.yaml +33 -0
- contextsynapse/tests/regression/cases/test_semantic_hash_function.yaml +30 -0
- contextsynapse/tests/regression/cases/test_semantic_hash_with_model.yaml +31 -0
- contextsynapse/tests/regression/cases/test_tcs_ingestion_fixed_size.yaml +44 -0
- contextsynapse/tests/regression/cases/test_tcs_ingestion_recursive.yaml +46 -0
- contextsynapse/tests/regression/cases/test_tcs_ingestion_section.yaml +46 -0
- contextsynapse/tests/regression/cases/test_tcs_ingestion_semantic.yaml +44 -0
- contextsynapse/tests/regression/cases/test_time_travel_as_of.yaml +35 -0
- contextsynapse/tests/regression/cases/test_time_travel_at_commit.yaml +33 -0
- contextsynapse/tests/regression/cases/test_time_travel_at_timestamp.yaml +33 -0
- contextsynapse/tests/regression/cases/test_time_travel_for_system_time_all.yaml +37 -0
- contextsynapse/tests/regression/cases/test_time_travel_versions_between.yaml +39 -0
- contextsynapse/tests/regression/cases/test_traverse_basic.yaml +31 -0
- contextsynapse/tests/regression/cases/test_traverse_max_depth.yaml +34 -0
- contextsynapse/tests/regression/cases/test_update_edge_basic.yaml +29 -0
- contextsynapse/tests/regression/evaluation_metrics.py +227 -0
- contextsynapse/tests/regression/regression_runner.py +1081 -0
- contextsynapse/tests/regression/setup_regression_data.py +212 -0
- contextsynapse/tests/regression/test_basic_only.py +84 -0
- contextsynapse/tests/regression/test_namespace_creation.py +29 -0
- contextsynapse/tests/regression/test_single_fix.py +61 -0
- contextsynapse/tests/regression/test_single_query.py +20 -0
- contextsynapse/tests/regression/validate_test_syntax.py +179 -0
- contextsynapse/tests/test_advanced_features.py +347 -0
- contextsynapse/tests/test_aiql_summary.py +283 -0
- contextsynapse/tests/test_all_aiql_queries.py +948 -0
- contextsynapse/tests/test_all_create_queries.py +213 -0
- contextsynapse/tests/test_api_vs_native.py +280 -0
- contextsynapse/tests/test_chunking_dedup.py +199 -0
- contextsynapse/tests/test_connectors.py +171 -0
- contextsynapse/tests/test_edges_and_match.py +202 -0
- contextsynapse/tests/test_extraction_pipeline_integration.py +581 -0
- contextsynapse/tests/test_name_property.py +224 -0
- contextsynapse/tests/test_node_edge_metadata.py +250 -0
- contextsynapse/tests/test_playground_conn.py +114 -0
- contextsynapse/tests/test_query_cache.py +213 -0
- contextsynapse/tests/test_source_policy_auth.py +348 -0
- contextsynapse/tests/test_temporal_and_domain.py +224 -0
- contextsynapse/tests/test_time_travel_e2e.py +571 -0
- contextsynapse/tests/test_traversal_and_aggregation.py +275 -0
- contextsynapse/tools/__init__.py +36 -0
- contextsynapse/tools/a2a_tools.py +106 -0
- contextsynapse/tools/cognition_tools.py +118 -0
- contextsynapse/tools/context.py +905 -0
- contextsynapse/tools/formatting.py +92 -0
- contextsynapse/tools/gateway_tools.py +186 -0
- contextsynapse/tools/graph.py +1700 -0
- contextsynapse/tools/intelligence.py +235 -0
- contextsynapse/tools/memory.py +118 -0
- contextsynapse/tools/registry.py +373 -0
- contextsynapse/tools/shield_tools.py +159 -0
- contextsynapse/tools/tasks.py +341 -0
- contextsynapse/tools/workspace_tools.py +124 -0
- contextsynapse/utils/__init__.py +15 -0
- contextsynapse/utils/config_loader.py +198 -0
- contextsynapse/utils/env_loader.py +156 -0
- contextsynapse/utils/file_utils.py +288 -0
- contextsynapse/utils/logging_optimizer.py +119 -0
- contextsynapse/vector/__init__.py +30 -0
- contextsynapse/vector/chroma_vector_store.py +152 -0
- contextsynapse/vector/custom_vector_store.py +537 -0
- contextsynapse/vector/faiss_vector_store.py +296 -0
- contextsynapse/vector/qdrant_vector_store.py +163 -0
- contextsynapse/vector/vector_db.py +953 -0
- contextsynapse/vector/vector_db_manager.py +197 -0
- contextsynapse/workspace/__init__.py +27 -0
- contextsynapse/workspace/base.py +94 -0
- contextsynapse/workspace/git.py +228 -0
- contextsynapse/workspace/github.py +78 -0
- contextsynapse/workspace/local.py +73 -0
- contextsynapse/workspace/tools.py +222 -0
- contextsynapse-1.0.0.dist-info/METADATA +847 -0
- contextsynapse-1.0.0.dist-info/RECORD +776 -0
- contextsynapse-1.0.0.dist-info/WHEEL +5 -0
- contextsynapse-1.0.0.dist-info/entry_points.txt +4 -0
- contextsynapse-1.0.0.dist-info/licenses/LICENSE +200 -0
- contextsynapse-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,888 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Context Manager
|
|
3
|
+
===============
|
|
4
|
+
Independent, first-class Context entities backed by graph namespaces.
|
|
5
|
+
|
|
6
|
+
A Context is a curated, typed knowledge store that wraps a graph namespace
|
|
7
|
+
with metadata, access control, and lifecycle management.
|
|
8
|
+
|
|
9
|
+
Architecture:
|
|
10
|
+
Graph (storage engine) → Context (curation layer) → Session (collaboration)
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
import logging
|
|
17
|
+
import re
|
|
18
|
+
import secrets
|
|
19
|
+
import sqlite3
|
|
20
|
+
from dataclasses import dataclass, field, asdict
|
|
21
|
+
from datetime import datetime, timezone
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Any, Dict, List, Optional
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
CONTEXT_NS_PREFIX = ""
|
|
28
|
+
|
|
29
|
+
VALID_CONTEXT_TYPES = {"user", "system", "knowledge_base", "document", "web", "generated", "software_dev"}
|
|
30
|
+
VALID_SENSITIVITIES = {"public", "internal", "confidential", "restricted"}
|
|
31
|
+
VALID_STATUSES = {"active", "archived", "deleted"}
|
|
32
|
+
|
|
33
|
+
from .context_schema import get_categories as _get_schema_categories
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class Context:
|
|
38
|
+
"""A first-class context entity."""
|
|
39
|
+
context_id: str
|
|
40
|
+
name: str
|
|
41
|
+
description: str
|
|
42
|
+
context_type: str # user | system | knowledge_base | document | web | generated
|
|
43
|
+
graph_namespace: str
|
|
44
|
+
source: str # manual | document:<file> | web:<url> | session:<id>
|
|
45
|
+
sensitivity: str # public | internal | confidential | restricted
|
|
46
|
+
owner_id: Optional[str]
|
|
47
|
+
status: str # active | archived | deleted
|
|
48
|
+
item_count: int
|
|
49
|
+
estimated_tokens: int
|
|
50
|
+
embedding_model: Optional[str] # e.g. "text-embedding-3-small", "all-MiniLM-L6-v2"
|
|
51
|
+
embedding_dimension: Optional[int] # e.g. 384, 768, 1536
|
|
52
|
+
tags: List[str] = field(default_factory=list)
|
|
53
|
+
created_at: str = ""
|
|
54
|
+
updated_at: str = ""
|
|
55
|
+
config: Dict[str, Any] = field(default_factory=dict)
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def security_config(self) -> Dict[str, Any]:
|
|
59
|
+
"""Get the security configuration for this context.
|
|
60
|
+
|
|
61
|
+
Stored in config['security']. Returns defaults if not set.
|
|
62
|
+
"""
|
|
63
|
+
defaults = {
|
|
64
|
+
"pii_detection": True,
|
|
65
|
+
"auto_tagging": True,
|
|
66
|
+
"encryption": False,
|
|
67
|
+
"redaction_mode": "mask",
|
|
68
|
+
"encrypt_fields": ["email", "phone", "ssn"],
|
|
69
|
+
}
|
|
70
|
+
return {**defaults, **self.config.get("security", {})}
|
|
71
|
+
|
|
72
|
+
@security_config.setter
|
|
73
|
+
def security_config(self, value: Dict[str, Any]):
|
|
74
|
+
self.config["security"] = value
|
|
75
|
+
|
|
76
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
77
|
+
return asdict(self)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _slugify(name: str) -> str:
|
|
81
|
+
"""Create a safe namespace slug from a context name."""
|
|
82
|
+
slug = re.sub(r"[^a-zA-Z0-9_-]", "_", name.lower().strip())
|
|
83
|
+
return slug[:64]
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class ContextManager:
|
|
87
|
+
"""
|
|
88
|
+
Manages independent Context entities backed by graph namespaces.
|
|
89
|
+
|
|
90
|
+
Each Context maps to a graph namespace via GraphRegistry.
|
|
91
|
+
SQLite-backed, same pattern as ContextSessionManager.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
_SCHEMA = """
|
|
95
|
+
CREATE TABLE IF NOT EXISTS contexts (
|
|
96
|
+
context_id TEXT PRIMARY KEY,
|
|
97
|
+
name TEXT NOT NULL,
|
|
98
|
+
description TEXT DEFAULT '',
|
|
99
|
+
context_type TEXT NOT NULL DEFAULT 'knowledge_base',
|
|
100
|
+
graph_namespace TEXT NOT NULL UNIQUE,
|
|
101
|
+
source TEXT DEFAULT 'manual',
|
|
102
|
+
sensitivity TEXT DEFAULT 'public',
|
|
103
|
+
owner_id TEXT,
|
|
104
|
+
status TEXT DEFAULT 'active',
|
|
105
|
+
item_count INTEGER DEFAULT 0,
|
|
106
|
+
estimated_tokens INTEGER DEFAULT 0,
|
|
107
|
+
embedding_model TEXT,
|
|
108
|
+
embedding_dimension INTEGER,
|
|
109
|
+
tags TEXT DEFAULT '[]',
|
|
110
|
+
created_at TEXT NOT NULL,
|
|
111
|
+
updated_at TEXT NOT NULL,
|
|
112
|
+
config TEXT DEFAULT '{}'
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
CREATE INDEX IF NOT EXISTS idx_contexts_status ON contexts(status);
|
|
116
|
+
CREATE INDEX IF NOT EXISTS idx_contexts_type ON contexts(context_type);
|
|
117
|
+
CREATE INDEX IF NOT EXISTS idx_contexts_owner ON contexts(owner_id);
|
|
118
|
+
CREATE INDEX IF NOT EXISTS idx_contexts_namespace ON contexts(graph_namespace);
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
def __init__(self, db_path: str = "contextcore_data/context.db", graph_registry=None):
|
|
122
|
+
self._db_path = Path(db_path)
|
|
123
|
+
self._db_path.parent.mkdir(parents=True, exist_ok=True)
|
|
124
|
+
self._conn = sqlite3.connect(str(self._db_path), check_same_thread=False)
|
|
125
|
+
self._conn.row_factory = sqlite3.Row
|
|
126
|
+
self._conn.executescript(self._SCHEMA)
|
|
127
|
+
self._migrate_embedding_columns()
|
|
128
|
+
self._graph_registry = graph_registry
|
|
129
|
+
|
|
130
|
+
def _migrate_embedding_columns(self):
|
|
131
|
+
"""Add embedding and tags columns if missing (for existing DBs)."""
|
|
132
|
+
try:
|
|
133
|
+
self._conn.execute("SELECT embedding_model FROM contexts LIMIT 1")
|
|
134
|
+
except sqlite3.OperationalError:
|
|
135
|
+
self._conn.execute("ALTER TABLE contexts ADD COLUMN embedding_model TEXT")
|
|
136
|
+
self._conn.execute("ALTER TABLE contexts ADD COLUMN embedding_dimension INTEGER")
|
|
137
|
+
self._conn.commit()
|
|
138
|
+
try:
|
|
139
|
+
self._conn.execute("SELECT tags FROM contexts LIMIT 1")
|
|
140
|
+
except sqlite3.OperationalError:
|
|
141
|
+
self._conn.execute("ALTER TABLE contexts ADD COLUMN tags TEXT DEFAULT '[]'")
|
|
142
|
+
self._conn.commit()
|
|
143
|
+
|
|
144
|
+
# ------------------------------------------------------------------
|
|
145
|
+
# CRUD
|
|
146
|
+
# ------------------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
def create_context(
|
|
149
|
+
self,
|
|
150
|
+
name: str,
|
|
151
|
+
context_type: str = "knowledge_base",
|
|
152
|
+
description: str = "",
|
|
153
|
+
source: str = "manual",
|
|
154
|
+
sensitivity: str = "public",
|
|
155
|
+
owner_id: Optional[str] = None,
|
|
156
|
+
embedding_model: Optional[str] = None,
|
|
157
|
+
embedding_dimension: Optional[int] = None,
|
|
158
|
+
tags: Optional[List[str]] = None,
|
|
159
|
+
config: Optional[Dict[str, Any]] = None,
|
|
160
|
+
_graph_namespace: Optional[str] = None,
|
|
161
|
+
) -> Context:
|
|
162
|
+
"""Create a new context with a backing graph namespace."""
|
|
163
|
+
if context_type not in VALID_CONTEXT_TYPES:
|
|
164
|
+
raise ValueError(f"Invalid context_type: {context_type}")
|
|
165
|
+
if sensitivity not in VALID_SENSITIVITIES:
|
|
166
|
+
raise ValueError(f"Invalid sensitivity: {sensitivity}")
|
|
167
|
+
|
|
168
|
+
# Enforce unique context names (case-insensitive)
|
|
169
|
+
existing = self._conn.execute(
|
|
170
|
+
"SELECT context_id FROM contexts WHERE LOWER(name) = LOWER(?) AND status != 'deleted'",
|
|
171
|
+
(name,),
|
|
172
|
+
).fetchone()
|
|
173
|
+
if existing:
|
|
174
|
+
raise ValueError(f"A context named '{name}' already exists")
|
|
175
|
+
|
|
176
|
+
context_id = secrets.token_hex(12)
|
|
177
|
+
now = datetime.now(timezone.utc).isoformat()
|
|
178
|
+
|
|
179
|
+
# Use provided namespace or generate from context name
|
|
180
|
+
if _graph_namespace:
|
|
181
|
+
ns = _graph_namespace
|
|
182
|
+
else:
|
|
183
|
+
ns = _slugify(name)
|
|
184
|
+
# Only collide if another context already owns this namespace
|
|
185
|
+
# (don't check graph registry — unrelated graphs like 'default' would cause false collisions)
|
|
186
|
+
existing_ns = self._conn.execute(
|
|
187
|
+
"SELECT context_id FROM contexts WHERE graph_namespace = ?", (ns,)
|
|
188
|
+
).fetchone()
|
|
189
|
+
if existing_ns:
|
|
190
|
+
ns = f"{ns}_{context_id[:6]}"
|
|
191
|
+
|
|
192
|
+
# Purge any stale soft-deleted rows that hold this namespace
|
|
193
|
+
# (legacy data from before hard-delete was implemented)
|
|
194
|
+
self._conn.execute(
|
|
195
|
+
"DELETE FROM contexts WHERE graph_namespace = ? AND status = 'deleted'",
|
|
196
|
+
(ns,),
|
|
197
|
+
)
|
|
198
|
+
self._conn.commit()
|
|
199
|
+
|
|
200
|
+
# Create backing graph namespace with root Context node
|
|
201
|
+
if self._graph_registry:
|
|
202
|
+
if not _graph_namespace:
|
|
203
|
+
self._graph_registry.create_graph(ns)
|
|
204
|
+
try:
|
|
205
|
+
from ..core.graph_structures import GraphNode
|
|
206
|
+
db = self._graph_registry.get_graph(ns)
|
|
207
|
+
if not db:
|
|
208
|
+
db = self._graph_registry.create_graph(ns)
|
|
209
|
+
db.add_node(GraphNode(
|
|
210
|
+
id=context_id,
|
|
211
|
+
label="Context",
|
|
212
|
+
properties={
|
|
213
|
+
"name": name,
|
|
214
|
+
"context_type": context_type,
|
|
215
|
+
"description": description[:200] if description else "",
|
|
216
|
+
"sensitivity": sensitivity,
|
|
217
|
+
"created_at": now,
|
|
218
|
+
},
|
|
219
|
+
), write_through=True)
|
|
220
|
+
|
|
221
|
+
# Create ALL category nodes under Context root (from schema)
|
|
222
|
+
from ..core.graph_structures import GraphEdge
|
|
223
|
+
schema_cats = _get_schema_categories()
|
|
224
|
+
for cat_key, (cat_label, cat_edge, _children) in schema_cats.items():
|
|
225
|
+
cat_node_id = f"{context_id}_{cat_key}"
|
|
226
|
+
db.add_node(GraphNode(
|
|
227
|
+
id=cat_node_id,
|
|
228
|
+
label=cat_label,
|
|
229
|
+
properties={
|
|
230
|
+
"name": f"{name} — {cat_label}",
|
|
231
|
+
"category": cat_key,
|
|
232
|
+
"created_at": now,
|
|
233
|
+
},
|
|
234
|
+
), write_through=True)
|
|
235
|
+
db.add_edge(GraphEdge(
|
|
236
|
+
id=f"{context_id}_to_{cat_key}",
|
|
237
|
+
source=context_id,
|
|
238
|
+
target=cat_node_id,
|
|
239
|
+
label=cat_edge,
|
|
240
|
+
properties={},
|
|
241
|
+
))
|
|
242
|
+
|
|
243
|
+
# ── Search index pointer nodes ──
|
|
244
|
+
# VectorIndex — tells search tools where embeddings live
|
|
245
|
+
vec_node_id = f"{context_id}_vector_index"
|
|
246
|
+
vec_collection = f"{ns}_passages"
|
|
247
|
+
db.add_node(GraphNode(
|
|
248
|
+
id=vec_node_id, label="VectorIndex",
|
|
249
|
+
properties={
|
|
250
|
+
"name": f"Vector Index: {name}",
|
|
251
|
+
"context_id": context_id,
|
|
252
|
+
"collection": vec_collection,
|
|
253
|
+
"embedding_model": embedding_model or "",
|
|
254
|
+
"embedding_dimension": embedding_dimension or 0,
|
|
255
|
+
"status": "pending", # becomes "active" after EMBED stage
|
|
256
|
+
"count": 0,
|
|
257
|
+
"created_at": now,
|
|
258
|
+
},
|
|
259
|
+
), write_through=True)
|
|
260
|
+
db.add_edge(GraphEdge(
|
|
261
|
+
id=f"{context_id}_to_vector_index",
|
|
262
|
+
source=context_id, target=vec_node_id,
|
|
263
|
+
label="HAS_INDEX", properties={"index_type": "vector"},
|
|
264
|
+
))
|
|
265
|
+
|
|
266
|
+
# BM25Index — tells search tools where full-text index lives
|
|
267
|
+
bm25_node_id = f"{context_id}_bm25_index"
|
|
268
|
+
safe_ns = ns.replace(":", "_")
|
|
269
|
+
bm25_path = f"contextcore_data/bm25_index/{safe_ns}"
|
|
270
|
+
db.add_node(GraphNode(
|
|
271
|
+
id=bm25_node_id, label="BM25Index",
|
|
272
|
+
properties={
|
|
273
|
+
"name": f"BM25 Index: {name}",
|
|
274
|
+
"context_id": context_id,
|
|
275
|
+
"index_path": bm25_path,
|
|
276
|
+
"graph_namespace": ns,
|
|
277
|
+
"status": "pending", # becomes "active" after INDEX_BM25 stage
|
|
278
|
+
"count": 0,
|
|
279
|
+
"created_at": now,
|
|
280
|
+
},
|
|
281
|
+
), write_through=True)
|
|
282
|
+
db.add_edge(GraphEdge(
|
|
283
|
+
id=f"{context_id}_to_bm25_index",
|
|
284
|
+
source=context_id, target=bm25_node_id,
|
|
285
|
+
label="HAS_INDEX", properties={"index_type": "bm25"},
|
|
286
|
+
))
|
|
287
|
+
|
|
288
|
+
# Save to disk (synchronous)
|
|
289
|
+
self._graph_registry.save_graph(ns, create_checkpoint=False)
|
|
290
|
+
except Exception as e:
|
|
291
|
+
logging.getLogger(__name__).debug("Failed to create root context node: %s", e)
|
|
292
|
+
|
|
293
|
+
merged_config = config or {}
|
|
294
|
+
|
|
295
|
+
# Software dev context: seed ProjectSpec node linked to root Context
|
|
296
|
+
if context_type == "software_dev" and self._graph_registry:
|
|
297
|
+
try:
|
|
298
|
+
db = self._graph_registry.get_graph(ns)
|
|
299
|
+
if db:
|
|
300
|
+
import uuid as _uuid
|
|
301
|
+
from ..core.graph_structures import GraphNode, GraphEdge
|
|
302
|
+
spec_id = str(_uuid.uuid4())
|
|
303
|
+
stack = merged_config.get("stack", {})
|
|
304
|
+
rules = merged_config.get("rules", [])
|
|
305
|
+
spec_text = merged_config.get("spec", description)
|
|
306
|
+
db.add_node(GraphNode(
|
|
307
|
+
id=spec_id, label="ProjectSpec",
|
|
308
|
+
properties={
|
|
309
|
+
"name": name,
|
|
310
|
+
"spec": spec_text,
|
|
311
|
+
"stack": json.dumps(stack) if isinstance(stack, dict) else str(stack),
|
|
312
|
+
"rules": json.dumps(rules) if isinstance(rules, list) else str(rules),
|
|
313
|
+
"repo": merged_config.get("repo", ""),
|
|
314
|
+
"status": "active",
|
|
315
|
+
"created_at": now,
|
|
316
|
+
},
|
|
317
|
+
), write_through=True)
|
|
318
|
+
# Link: Context ──HAS_SPEC──► ProjectSpec
|
|
319
|
+
db.add_edge(GraphEdge(
|
|
320
|
+
id=str(_uuid.uuid4()), source=context_id, target=spec_id,
|
|
321
|
+
label="HAS_SPEC", properties={},
|
|
322
|
+
))
|
|
323
|
+
# Store metadata in namespace store
|
|
324
|
+
try:
|
|
325
|
+
from ..storage.namespace_store import NamespaceStore
|
|
326
|
+
ns_store = NamespaceStore(ns)
|
|
327
|
+
ns_store.set_metadata("spec", spec_text)
|
|
328
|
+
ns_store.set_metadata("stack", stack)
|
|
329
|
+
ns_store.set_metadata("rules", rules)
|
|
330
|
+
ns_store.close()
|
|
331
|
+
except Exception:
|
|
332
|
+
pass
|
|
333
|
+
except Exception as e:
|
|
334
|
+
logging.getLogger(__name__).warning("Failed to seed code context: %s", e)
|
|
335
|
+
|
|
336
|
+
ctx = Context(
|
|
337
|
+
context_id=context_id,
|
|
338
|
+
name=name,
|
|
339
|
+
description=description,
|
|
340
|
+
context_type=context_type,
|
|
341
|
+
graph_namespace=ns,
|
|
342
|
+
source=source,
|
|
343
|
+
sensitivity=sensitivity,
|
|
344
|
+
owner_id=owner_id,
|
|
345
|
+
status="active",
|
|
346
|
+
item_count=0,
|
|
347
|
+
estimated_tokens=0,
|
|
348
|
+
embedding_model=embedding_model,
|
|
349
|
+
embedding_dimension=embedding_dimension,
|
|
350
|
+
tags=tags or [],
|
|
351
|
+
created_at=now,
|
|
352
|
+
updated_at=now,
|
|
353
|
+
config=merged_config,
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
self._conn.execute(
|
|
357
|
+
"INSERT INTO contexts "
|
|
358
|
+
"(context_id, name, description, context_type, graph_namespace, "
|
|
359
|
+
"source, sensitivity, owner_id, status, item_count, estimated_tokens, "
|
|
360
|
+
"embedding_model, embedding_dimension, tags, "
|
|
361
|
+
"created_at, updated_at, config) "
|
|
362
|
+
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
363
|
+
(
|
|
364
|
+
ctx.context_id, ctx.name, ctx.description, ctx.context_type,
|
|
365
|
+
ctx.graph_namespace, ctx.source, ctx.sensitivity, ctx.owner_id,
|
|
366
|
+
ctx.status, ctx.item_count, ctx.estimated_tokens,
|
|
367
|
+
ctx.embedding_model, ctx.embedding_dimension,
|
|
368
|
+
json.dumps(ctx.tags),
|
|
369
|
+
ctx.created_at, ctx.updated_at, json.dumps(ctx.config),
|
|
370
|
+
),
|
|
371
|
+
)
|
|
372
|
+
self._conn.commit()
|
|
373
|
+
|
|
374
|
+
# Graph-first: register context + connect to its type node
|
|
375
|
+
if self._graph_registry:
|
|
376
|
+
try:
|
|
377
|
+
from ..core.graph_structures import GraphNode, GraphEdge
|
|
378
|
+
import uuid as _uuid
|
|
379
|
+
default_graph = self._graph_registry.get_graph("default") or self._graph_registry.create_graph("default")
|
|
380
|
+
|
|
381
|
+
# Ensure ContextType node exists (one per type, reused)
|
|
382
|
+
type_node_id = f"context_type_{context_type}"
|
|
383
|
+
existing_type = None
|
|
384
|
+
for n in default_graph.get_all_nodes():
|
|
385
|
+
if getattr(n, 'id', '') == type_node_id:
|
|
386
|
+
existing_type = n
|
|
387
|
+
break
|
|
388
|
+
if not existing_type:
|
|
389
|
+
default_graph.add_node(GraphNode(
|
|
390
|
+
id=type_node_id,
|
|
391
|
+
label="ContextType",
|
|
392
|
+
properties={"name": context_type, "label": context_type.replace("_", " ").title()},
|
|
393
|
+
), write_through=True)
|
|
394
|
+
|
|
395
|
+
# Create Context node
|
|
396
|
+
default_graph.add_node(GraphNode(
|
|
397
|
+
id=ctx.context_id,
|
|
398
|
+
label="Context",
|
|
399
|
+
properties={
|
|
400
|
+
"name": ctx.name,
|
|
401
|
+
"context_type": ctx.context_type,
|
|
402
|
+
"graph_namespace": ctx.graph_namespace,
|
|
403
|
+
"description": ctx.description[:200] if ctx.description else "",
|
|
404
|
+
"sensitivity": ctx.sensitivity,
|
|
405
|
+
"status": ctx.status,
|
|
406
|
+
"created_at": ctx.created_at,
|
|
407
|
+
},
|
|
408
|
+
), write_through=True)
|
|
409
|
+
|
|
410
|
+
# Connect: Context ──IS_TYPE──► ContextType
|
|
411
|
+
default_graph.add_edge(GraphEdge(
|
|
412
|
+
id=str(_uuid.uuid4()),
|
|
413
|
+
source=ctx.context_id,
|
|
414
|
+
target=type_node_id,
|
|
415
|
+
label="IS_TYPE",
|
|
416
|
+
properties={},
|
|
417
|
+
))
|
|
418
|
+
except Exception as e:
|
|
419
|
+
logging.getLogger(__name__).debug("Failed to register context in default graph: %s", e)
|
|
420
|
+
|
|
421
|
+
return ctx
|
|
422
|
+
|
|
423
|
+
def ensure_boundaries(self, context_id: str) -> Optional[Dict[str, Any]]:
|
|
424
|
+
"""Ensure a context has boundary nodes; seed them if missing.
|
|
425
|
+
|
|
426
|
+
Returns the boundary IDs dict, or None if the context doesn't exist.
|
|
427
|
+
"""
|
|
428
|
+
ctx = self.get_context(context_id)
|
|
429
|
+
if not ctx:
|
|
430
|
+
return None
|
|
431
|
+
|
|
432
|
+
existing = ctx.config.get("_boundary_ids")
|
|
433
|
+
if existing:
|
|
434
|
+
return existing
|
|
435
|
+
|
|
436
|
+
if not self._graph_registry:
|
|
437
|
+
return None
|
|
438
|
+
|
|
439
|
+
db = self._graph_registry.get_graph(ctx.graph_namespace)
|
|
440
|
+
if not db:
|
|
441
|
+
return None
|
|
442
|
+
|
|
443
|
+
try:
|
|
444
|
+
from .boundaries import seed_boundary_nodes
|
|
445
|
+
boundary_ids = seed_boundary_nodes(db, ctx.context_id, ctx.name)
|
|
446
|
+
self._graph_registry.save_graph(ctx.graph_namespace, create_checkpoint=False)
|
|
447
|
+
# Persist to context config
|
|
448
|
+
new_config = dict(ctx.config)
|
|
449
|
+
new_config["_boundary_ids"] = boundary_ids
|
|
450
|
+
self.update_context(context_id, config=new_config)
|
|
451
|
+
return boundary_ids
|
|
452
|
+
except Exception as e:
|
|
453
|
+
logging.getLogger(__name__).warning("Failed to ensure boundaries for %s: %s", context_id, e)
|
|
454
|
+
return None
|
|
455
|
+
|
|
456
|
+
def get_context(self, context_id: str) -> Optional[Context]:
|
|
457
|
+
row = self._conn.execute(
|
|
458
|
+
"SELECT * FROM contexts WHERE context_id = ? AND status != 'deleted'",
|
|
459
|
+
(context_id,),
|
|
460
|
+
).fetchone()
|
|
461
|
+
return self._row_to_context(row) if row else None
|
|
462
|
+
|
|
463
|
+
def get_by_graph_namespace(self, namespace: str) -> Optional[Context]:
|
|
464
|
+
row = self._conn.execute(
|
|
465
|
+
"SELECT * FROM contexts WHERE graph_namespace = ? AND status != 'deleted'",
|
|
466
|
+
(namespace,),
|
|
467
|
+
).fetchone()
|
|
468
|
+
return self._row_to_context(row) if row else None
|
|
469
|
+
|
|
470
|
+
def list_contexts(
|
|
471
|
+
self,
|
|
472
|
+
context_type: Optional[str] = None,
|
|
473
|
+
status: str = "active",
|
|
474
|
+
owner_id: Optional[str] = None,
|
|
475
|
+
search: Optional[str] = None,
|
|
476
|
+
tag: Optional[str] = None,
|
|
477
|
+
) -> List[Context]:
|
|
478
|
+
query = "SELECT * FROM contexts WHERE status = ?"
|
|
479
|
+
params: list = [status]
|
|
480
|
+
|
|
481
|
+
if context_type:
|
|
482
|
+
query += " AND context_type = ?"
|
|
483
|
+
params.append(context_type)
|
|
484
|
+
if owner_id:
|
|
485
|
+
query += " AND owner_id = ?"
|
|
486
|
+
params.append(owner_id)
|
|
487
|
+
if search:
|
|
488
|
+
query += " AND (name LIKE ? OR description LIKE ? OR tags LIKE ?)"
|
|
489
|
+
like = f"%{search}%"
|
|
490
|
+
params.extend([like, like, like])
|
|
491
|
+
if tag:
|
|
492
|
+
query += " AND tags LIKE ?"
|
|
493
|
+
params.append(f'%"{tag}"%')
|
|
494
|
+
|
|
495
|
+
query += " ORDER BY updated_at DESC"
|
|
496
|
+
rows = self._conn.execute(query, params).fetchall()
|
|
497
|
+
return [self._row_to_context(r) for r in rows]
|
|
498
|
+
|
|
499
|
+
def update_context(self, context_id: str, **fields) -> Optional[Context]:
|
|
500
|
+
"""Update mutable fields on a context."""
|
|
501
|
+
allowed = {"name", "description", "sensitivity", "context_type", "source", "config", "status", "embedding_model", "embedding_dimension", "tags"}
|
|
502
|
+
updates = {k: v for k, v in fields.items() if k in allowed and v is not None}
|
|
503
|
+
if not updates:
|
|
504
|
+
return self.get_context(context_id)
|
|
505
|
+
|
|
506
|
+
now = datetime.now(timezone.utc).isoformat()
|
|
507
|
+
updates["updated_at"] = now
|
|
508
|
+
|
|
509
|
+
# Serialize config/tags if present
|
|
510
|
+
if "config" in updates and isinstance(updates["config"], dict):
|
|
511
|
+
updates["config"] = json.dumps(updates["config"])
|
|
512
|
+
if "tags" in updates and isinstance(updates["tags"], list):
|
|
513
|
+
updates["tags"] = json.dumps(updates["tags"])
|
|
514
|
+
|
|
515
|
+
set_clause = ", ".join(f"{k} = ?" for k in updates)
|
|
516
|
+
values = list(updates.values()) + [context_id]
|
|
517
|
+
|
|
518
|
+
self._conn.execute(
|
|
519
|
+
f"UPDATE contexts SET {set_clause} WHERE context_id = ?",
|
|
520
|
+
values,
|
|
521
|
+
)
|
|
522
|
+
self._conn.commit()
|
|
523
|
+
return self.get_context(context_id)
|
|
524
|
+
|
|
525
|
+
# ------------------------------------------------------------------
|
|
526
|
+
# Per-context extraction schema overrides
|
|
527
|
+
# ------------------------------------------------------------------
|
|
528
|
+
|
|
529
|
+
def get_extraction_schema(self, context_id: str) -> Optional[str]:
|
|
530
|
+
"""Get the custom extraction schema YAML for a context, or None for default."""
|
|
531
|
+
ctx = self.get_context(context_id)
|
|
532
|
+
if not ctx:
|
|
533
|
+
return None
|
|
534
|
+
return ctx.config.get("extraction_schema_yaml")
|
|
535
|
+
|
|
536
|
+
def set_extraction_schema(self, context_id: str, schema_yaml: str) -> bool:
|
|
537
|
+
"""Set a custom extraction schema YAML on a context."""
|
|
538
|
+
ctx = self.get_context(context_id)
|
|
539
|
+
if not ctx:
|
|
540
|
+
return False
|
|
541
|
+
new_config = dict(ctx.config)
|
|
542
|
+
new_config["extraction_schema_yaml"] = schema_yaml
|
|
543
|
+
self.update_context(context_id, config=new_config)
|
|
544
|
+
return True
|
|
545
|
+
|
|
546
|
+
def clear_extraction_schema(self, context_id: str) -> bool:
|
|
547
|
+
"""Remove the custom extraction schema, reverting to the type default."""
|
|
548
|
+
ctx = self.get_context(context_id)
|
|
549
|
+
if not ctx:
|
|
550
|
+
return False
|
|
551
|
+
new_config = dict(ctx.config)
|
|
552
|
+
new_config.pop("extraction_schema_yaml", None)
|
|
553
|
+
self.update_context(context_id, config=new_config)
|
|
554
|
+
return True
|
|
555
|
+
|
|
556
|
+
def delete_context(self, context_id: str) -> bool:
|
|
557
|
+
"""Delete a context — cascades to its backing graph."""
|
|
558
|
+
ctx = self.get_context(context_id)
|
|
559
|
+
if not ctx:
|
|
560
|
+
return False
|
|
561
|
+
|
|
562
|
+
now = datetime.now(timezone.utc).isoformat()
|
|
563
|
+
|
|
564
|
+
# 1. Delete backing graph (memory + files)
|
|
565
|
+
if self._graph_registry and ctx.graph_namespace:
|
|
566
|
+
try:
|
|
567
|
+
self._graph_registry.delete_graph(ctx.graph_namespace, delete_files=True)
|
|
568
|
+
except Exception as e:
|
|
569
|
+
logging.getLogger(__name__).debug("Failed to delete backing graph: %s", e)
|
|
570
|
+
|
|
571
|
+
# 2. Hard-delete from SQLite (graph files already gone, soft-delete
|
|
572
|
+
# leaves the UNIQUE graph_namespace occupied and blocks re-creation)
|
|
573
|
+
cur = self._conn.execute(
|
|
574
|
+
"DELETE FROM contexts WHERE context_id = ?",
|
|
575
|
+
(context_id,),
|
|
576
|
+
)
|
|
577
|
+
self._conn.commit()
|
|
578
|
+
return cur.rowcount > 0
|
|
579
|
+
|
|
580
|
+
# ------------------------------------------------------------------
|
|
581
|
+
# Content operations
|
|
582
|
+
# ------------------------------------------------------------------
|
|
583
|
+
|
|
584
|
+
def add_text(
|
|
585
|
+
self,
|
|
586
|
+
context_id: str,
|
|
587
|
+
content: str,
|
|
588
|
+
role: str = "user",
|
|
589
|
+
label: Optional[str] = None,
|
|
590
|
+
sensitivity: str = "public",
|
|
591
|
+
tags: Optional[List[str]] = None,
|
|
592
|
+
) -> Optional[Dict[str, Any]]:
|
|
593
|
+
"""Add a text item to the context's backing graph as a node."""
|
|
594
|
+
ctx = self.get_context(context_id)
|
|
595
|
+
if not ctx:
|
|
596
|
+
return None
|
|
597
|
+
|
|
598
|
+
if not self._graph_registry:
|
|
599
|
+
return None
|
|
600
|
+
|
|
601
|
+
db = self._graph_registry.get_graph(ctx.graph_namespace)
|
|
602
|
+
if not db:
|
|
603
|
+
self._graph_registry.create_graph(ctx.graph_namespace)
|
|
604
|
+
db = self._graph_registry.get_graph(ctx.graph_namespace)
|
|
605
|
+
|
|
606
|
+
if not db:
|
|
607
|
+
return None
|
|
608
|
+
|
|
609
|
+
from ..core.graph_structures import GraphNode, GraphEdge
|
|
610
|
+
import uuid
|
|
611
|
+
node_id = str(uuid.uuid4())
|
|
612
|
+
node = GraphNode(
|
|
613
|
+
id=node_id,
|
|
614
|
+
label="ContextItem",
|
|
615
|
+
properties={
|
|
616
|
+
"content": content,
|
|
617
|
+
"role": role,
|
|
618
|
+
"label": label or "",
|
|
619
|
+
"sensitivity": sensitivity,
|
|
620
|
+
"tags": json.dumps(tags or []),
|
|
621
|
+
"created_at": datetime.now(timezone.utc).isoformat(),
|
|
622
|
+
},
|
|
623
|
+
)
|
|
624
|
+
db.add_node(node, write_through=True)
|
|
625
|
+
|
|
626
|
+
# Link to type node (KnowledgeBase etc.) or Context root
|
|
627
|
+
type_node_id = f"{context_id}_knowledge"
|
|
628
|
+
parent_id = type_node_id if db.get_node(type_node_id) else context_id
|
|
629
|
+
db.add_edge(GraphEdge(
|
|
630
|
+
id=str(uuid.uuid4()),
|
|
631
|
+
source=parent_id,
|
|
632
|
+
target=node_id,
|
|
633
|
+
label="HAS_CONTENT",
|
|
634
|
+
properties={"role": role},
|
|
635
|
+
))
|
|
636
|
+
|
|
637
|
+
# Persist graph
|
|
638
|
+
try:
|
|
639
|
+
self._graph_registry.save_graph(ctx.graph_namespace, create_checkpoint=False)
|
|
640
|
+
except Exception as e:
|
|
641
|
+
logger.warning("Failed to persist context graph %s: %s", ctx.graph_namespace, e)
|
|
642
|
+
|
|
643
|
+
# Update stats
|
|
644
|
+
self._refresh_stats(context_id, db)
|
|
645
|
+
|
|
646
|
+
return {
|
|
647
|
+
"node_id": str(node.id),
|
|
648
|
+
"content": content[:100],
|
|
649
|
+
"role": role,
|
|
650
|
+
"label": label,
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
# Node labels relevant to each context type. When a context has a
|
|
654
|
+
# declared type we only return nodes whose label is in this set so
|
|
655
|
+
# the UI stays focused. ``None`` means "show everything".
|
|
656
|
+
# Connector-related node types shared across all context types
|
|
657
|
+
_CONNECTOR_LABELS = {
|
|
658
|
+
"Connector", "SyncRun", "DatabaseSchema", "Table", "Column", "Index", "ForeignKey",
|
|
659
|
+
"Repository", "Issue", "PullRequest", "File", "Contributor",
|
|
660
|
+
"JiraProject", "Sprint", "Component", "User",
|
|
661
|
+
"APIEndpoint", "ResponseField",
|
|
662
|
+
"SFObject", "SFField", "SFRelationship",
|
|
663
|
+
"SAPModule", "SAPTable", "SAPField",
|
|
664
|
+
"SNTable", "SNField",
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
_TYPE_RELEVANT_LABELS: Dict[str, Optional[set]] = {
|
|
668
|
+
"knowledge_base": {"Document", "Passage", "TextChunk", "Chunk", "Fact", "Concept", "Topic", "Knowledge"} | _CONNECTOR_LABELS,
|
|
669
|
+
"software_dev": {"CodeFile", "CodeModule", "Document", "Passage", "TextChunk", "Chunk", "Module", "Class",
|
|
670
|
+
"Function", "Component", "Service", "Schema", "API", "Endpoint",
|
|
671
|
+
"GitHubIssue", "GitHubPR", "Contributor", "RepoMeta",
|
|
672
|
+
"ArchDecision", "KnownIssue", "TestCase", "APIContract",
|
|
673
|
+
"Requirement", "UserStory", "Goal", "ChangeRecord"} | _CONNECTOR_LABELS,
|
|
674
|
+
"rules": {"Document", "Passage", "TextChunk", "Chunk", "Rule", "Constraint", "Standard", "Policy", "Fact"} | _CONNECTOR_LABELS,
|
|
675
|
+
"database": {"Table", "Column", "Schema", "Index", "View", "Document", "Passage", "TextChunk", "Chunk",
|
|
676
|
+
"Entity", "Relationship"} | _CONNECTOR_LABELS,
|
|
677
|
+
"decision": {"Decision", "Document", "Passage", "TextChunk", "Chunk", "Fact", "ADR", "TradeOff"} | _CONNECTOR_LABELS,
|
|
678
|
+
"web": {"Document", "Passage", "TextChunk", "Chunk", "WebPage", "Link", "Fact"} | _CONNECTOR_LABELS,
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
def get_items(self, context_id: str, category: Optional[str] = None,
|
|
682
|
+
show_all: bool = False, max_tokens: Optional[int] = None,
|
|
683
|
+
query: Optional[str] = None) -> List[Dict[str, Any]]:
|
|
684
|
+
"""Get content items from a context's backing graph.
|
|
685
|
+
|
|
686
|
+
Args:
|
|
687
|
+
context_id: The context to query.
|
|
688
|
+
category: Optional category filter.
|
|
689
|
+
show_all: If True, bypass context-type filtering.
|
|
690
|
+
max_tokens: If set, return items up to this token budget (most relevant first).
|
|
691
|
+
query: If set with max_tokens, rank items by relevance to this query.
|
|
692
|
+
|
|
693
|
+
Returns:
|
|
694
|
+
List of item dicts, excluding structural boundary nodes.
|
|
695
|
+
"""
|
|
696
|
+
from .boundaries import BOUNDARY_NODE_LABELS
|
|
697
|
+
|
|
698
|
+
ctx = self.get_context(context_id)
|
|
699
|
+
if not ctx or not self._graph_registry:
|
|
700
|
+
return []
|
|
701
|
+
|
|
702
|
+
ns = ctx.graph_namespace
|
|
703
|
+
db = self._graph_registry.get_graph(ns)
|
|
704
|
+
if not db:
|
|
705
|
+
logger.warning("get_items: graph '%s' not found for context '%s'", ns, context_id)
|
|
706
|
+
return []
|
|
707
|
+
|
|
708
|
+
# Determine which labels to show based on context type
|
|
709
|
+
relevant_labels = None
|
|
710
|
+
if not show_all:
|
|
711
|
+
# Try schema-driven labels first
|
|
712
|
+
try:
|
|
713
|
+
from contextsynapse.project.schema_manager import get_schema_manager
|
|
714
|
+
schema_labels = get_schema_manager().get_consumer_hint(
|
|
715
|
+
ns, "context_filter_labels", None
|
|
716
|
+
)
|
|
717
|
+
if schema_labels is not None:
|
|
718
|
+
relevant_labels = set(schema_labels) | self._CONNECTOR_LABELS
|
|
719
|
+
except Exception:
|
|
720
|
+
pass
|
|
721
|
+
# Fall back to hardcoded per-context-type labels
|
|
722
|
+
if relevant_labels is None:
|
|
723
|
+
relevant_labels = self._TYPE_RELEVANT_LABELS.get(ctx.context_type)
|
|
724
|
+
|
|
725
|
+
nodes = db.get_all_nodes()
|
|
726
|
+
items = []
|
|
727
|
+
for node in nodes:
|
|
728
|
+
if isinstance(node, dict):
|
|
729
|
+
props = node
|
|
730
|
+
node_label = props.get("label", "")
|
|
731
|
+
else:
|
|
732
|
+
node_label = getattr(node, "label", "")
|
|
733
|
+
props = getattr(node, "properties", {}) or {}
|
|
734
|
+
props["node_id"] = str(getattr(node, "id", ""))
|
|
735
|
+
props["label_type"] = node_label
|
|
736
|
+
|
|
737
|
+
# Skip structural boundary nodes
|
|
738
|
+
if node_label in BOUNDARY_NODE_LABELS:
|
|
739
|
+
continue
|
|
740
|
+
|
|
741
|
+
# Filter by context-type-relevant labels
|
|
742
|
+
if relevant_labels and node_label not in relevant_labels:
|
|
743
|
+
continue
|
|
744
|
+
|
|
745
|
+
# Apply category filter
|
|
746
|
+
if category and props.get("category") != category:
|
|
747
|
+
continue
|
|
748
|
+
|
|
749
|
+
items.append(props)
|
|
750
|
+
|
|
751
|
+
# Token-budgeted retrieval with relevance ranking
|
|
752
|
+
if max_tokens and items:
|
|
753
|
+
if query:
|
|
754
|
+
# Score by keyword relevance to query
|
|
755
|
+
q_terms = query.lower().split()
|
|
756
|
+
scored = []
|
|
757
|
+
for item in items:
|
|
758
|
+
text = " ".join(str(v) for v in item.values()).lower()
|
|
759
|
+
score = sum(1 for t in q_terms if t in text) / max(len(q_terms), 1)
|
|
760
|
+
scored.append((item, score))
|
|
761
|
+
scored.sort(key=lambda x: x[1], reverse=True)
|
|
762
|
+
items = [s[0] for s in scored]
|
|
763
|
+
|
|
764
|
+
# Early stopping at token budget
|
|
765
|
+
result = []
|
|
766
|
+
tokens_used = 0
|
|
767
|
+
for item in items:
|
|
768
|
+
content = item.get("content") or item.get("description") or item.get("statement") or ""
|
|
769
|
+
item_tokens = max(1, len(str(content)) // 4)
|
|
770
|
+
if tokens_used + item_tokens > max_tokens:
|
|
771
|
+
break
|
|
772
|
+
result.append(item)
|
|
773
|
+
tokens_used += item_tokens
|
|
774
|
+
return result
|
|
775
|
+
|
|
776
|
+
return items
|
|
777
|
+
|
|
778
|
+
def refresh_stats(self, context_id: str) -> Optional[Context]:
|
|
779
|
+
"""Recount items and estimate tokens from the backing graph."""
|
|
780
|
+
ctx = self.get_context(context_id)
|
|
781
|
+
if not ctx or not self._graph_registry:
|
|
782
|
+
return ctx
|
|
783
|
+
|
|
784
|
+
ns = ctx.graph_namespace
|
|
785
|
+
db = self._graph_registry.get_graph(ns)
|
|
786
|
+
if not db:
|
|
787
|
+
logger.warning("refresh_stats: graph '%s' not found for context '%s'", ns, context_id)
|
|
788
|
+
if db:
|
|
789
|
+
self._refresh_stats(context_id, db)
|
|
790
|
+
|
|
791
|
+
return self.get_context(context_id)
|
|
792
|
+
|
|
793
|
+
def _refresh_stats(self, context_id: str, db):
|
|
794
|
+
"""Internal: update item_count and estimated_tokens from graph."""
|
|
795
|
+
from .boundaries import BOUNDARY_NODE_LABELS
|
|
796
|
+
|
|
797
|
+
ctx = self.get_context(context_id)
|
|
798
|
+
relevant_labels = self._TYPE_RELEVANT_LABELS.get(ctx.context_type) if ctx else None
|
|
799
|
+
|
|
800
|
+
nodes = db.get_all_nodes()
|
|
801
|
+
item_count = 0
|
|
802
|
+
total_chars = 0
|
|
803
|
+
|
|
804
|
+
for node in nodes:
|
|
805
|
+
if isinstance(node, dict):
|
|
806
|
+
node_label = node.get("label", "")
|
|
807
|
+
content = node.get("content", "")
|
|
808
|
+
else:
|
|
809
|
+
node_label = getattr(node, "label", "")
|
|
810
|
+
props = getattr(node, "properties", {}) or {}
|
|
811
|
+
content = props.get("content", "")
|
|
812
|
+
|
|
813
|
+
# Exclude structural boundary nodes from stats
|
|
814
|
+
if node_label in BOUNDARY_NODE_LABELS:
|
|
815
|
+
continue
|
|
816
|
+
|
|
817
|
+
# Only count type-relevant nodes
|
|
818
|
+
if relevant_labels and node_label not in relevant_labels:
|
|
819
|
+
continue
|
|
820
|
+
|
|
821
|
+
item_count += 1
|
|
822
|
+
total_chars += len(str(content))
|
|
823
|
+
|
|
824
|
+
estimated_tokens = int(total_chars / 4.0) # ~4 chars per token
|
|
825
|
+
now = datetime.now(timezone.utc).isoformat()
|
|
826
|
+
|
|
827
|
+
self._conn.execute(
|
|
828
|
+
"UPDATE contexts SET item_count = ?, estimated_tokens = ?, updated_at = ? "
|
|
829
|
+
"WHERE context_id = ?",
|
|
830
|
+
(item_count, estimated_tokens, now, context_id),
|
|
831
|
+
)
|
|
832
|
+
self._conn.commit()
|
|
833
|
+
|
|
834
|
+
# ------------------------------------------------------------------
|
|
835
|
+
# Migration
|
|
836
|
+
# ------------------------------------------------------------------
|
|
837
|
+
|
|
838
|
+
def migrate_session_contexts(self, session_manager) -> int:
|
|
839
|
+
"""One-time migration: wrap each session's graph namespace as a Context.
|
|
840
|
+
|
|
841
|
+
Returns the number of contexts created.
|
|
842
|
+
"""
|
|
843
|
+
created = 0
|
|
844
|
+
for session in session_manager.list_sessions():
|
|
845
|
+
existing = self.get_by_graph_namespace(session.graph_namespace)
|
|
846
|
+
if not existing:
|
|
847
|
+
try:
|
|
848
|
+
self.create_context(
|
|
849
|
+
name=session.name,
|
|
850
|
+
context_type="generated",
|
|
851
|
+
description=f"Auto-migrated from session '{session.name}'",
|
|
852
|
+
source=f"session:{session.session_id}",
|
|
853
|
+
owner_id=session.owner_agent_id,
|
|
854
|
+
_graph_namespace=session.graph_namespace,
|
|
855
|
+
)
|
|
856
|
+
created += 1
|
|
857
|
+
except Exception as e:
|
|
858
|
+
logger.warning("Failed to migrate session '%s': %s", session.name, e)
|
|
859
|
+
return created
|
|
860
|
+
|
|
861
|
+
# ------------------------------------------------------------------
|
|
862
|
+
# Helpers
|
|
863
|
+
# ------------------------------------------------------------------
|
|
864
|
+
|
|
865
|
+
def _row_to_context(self, row: sqlite3.Row) -> Context:
|
|
866
|
+
return Context(
|
|
867
|
+
context_id=row["context_id"],
|
|
868
|
+
name=row["name"],
|
|
869
|
+
description=row["description"],
|
|
870
|
+
context_type=row["context_type"],
|
|
871
|
+
graph_namespace=row["graph_namespace"],
|
|
872
|
+
source=row["source"],
|
|
873
|
+
sensitivity=row["sensitivity"],
|
|
874
|
+
owner_id=row["owner_id"],
|
|
875
|
+
status=row["status"],
|
|
876
|
+
item_count=row["item_count"],
|
|
877
|
+
estimated_tokens=row["estimated_tokens"],
|
|
878
|
+
embedding_model=row["embedding_model"],
|
|
879
|
+
embedding_dimension=row["embedding_dimension"],
|
|
880
|
+
tags=json.loads(row["tags"]) if row["tags"] else [],
|
|
881
|
+
created_at=row["created_at"],
|
|
882
|
+
updated_at=row["updated_at"],
|
|
883
|
+
config=json.loads(row["config"]) if row["config"] else {},
|
|
884
|
+
)
|
|
885
|
+
|
|
886
|
+
def close(self):
|
|
887
|
+
if self._conn:
|
|
888
|
+
self._conn.close()
|