topos-node 1.3.2__tar.gz → 1.3.3__tar.gz
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.
- {topos_node-1.3.2/topos_node.egg-info → topos_node-1.3.3}/PKG-INFO +1 -1
- {topos_node-1.3.2 → topos_node-1.3.3}/pyproject.toml +1 -1
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/__version__.py +2 -2
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/core/handlers/signal_features.py +40 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/core/state.py +34 -2
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/triage/dashboard.py +9 -1
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/triage/intents.py +83 -6
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/protocol/handled_message_types.json +2 -0
- topos_node-1.3.3/topos/storage/db/migrations/__init__.py +190 -0
- topos_node-1.3.3/topos/storage/db/migrations/backup.py +125 -0
- topos_node-1.3.3/topos/storage/db/migrations/registry.py +318 -0
- topos_node-1.3.3/topos/storage/db/migrations/registry_checksums.json +31 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/upgrades/__init__.py +49 -15
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/upgrades/manifests.json +40 -10
- {topos_node-1.3.2 → topos_node-1.3.3/topos_node.egg-info}/PKG-INFO +1 -1
- {topos_node-1.3.2 → topos_node-1.3.3}/topos_node.egg-info/SOURCES.txt +3 -0
- topos_node-1.3.2/topos/storage/db/migrations/__init__.py +0 -326
- {topos_node-1.3.2 → topos_node-1.3.3}/LICENSE +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/MANIFEST.in +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/README.md +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/setup.cfg +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/shared/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/shared/filtering.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/shared/schema_registry.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/tests/test_extensions.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/tests/test_llm_integrations_storage.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/tests/test_protocol_handled_types.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/analytics/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/analytics/duckdb_adapter.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/analytics/messenger_communities.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/analytics/messenger_graph.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/analytics/messenger_labels.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/analytics/profiles.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/analytics/query_engine.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/analytics/raw_queries.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/api/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/api/analytics.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/api/app_registry.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/api/backup.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/api/compute_remote.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/api/conversation_context.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/api/data_commit.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/api/data_explorer_table_prefs.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/api/database_explorer.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/api/db.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/api/device.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/api/engine_run.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/api/enrichment.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/api/enrichment_lab.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/api/facts_llm_config.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/api/filter_lab.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/api/health.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/api/home_chat.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/api/ingestion_api.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/api/ingestion_compat.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/api/ingestion_sources.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/api/intelligence_lifecycle.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/api/llm.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/api/local_mcp.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/api/messenger_analytics.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/api/privacy_disclose.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/api/query_api.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/api/runtime_bootstrap.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/api/sanitization_ollama_config.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/api/shell.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/api/signal.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/api/signal_extraction_config.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/api/source_install.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/api/source_scrub.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/api/sources.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/api/sources_overview.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/api/sync.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/api/tool_index.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/api/ui_config.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/api/uma_data.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/api/upgrades.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/api/usage.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/api/user_identity.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/app.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/auth.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/canonicalization/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/canonicalization/mappers/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/canonicalization/mappers/base.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/canonicalization/mappers/browser_activity_mapper.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/canonicalization/mappers/chatgpt_mapper.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/canonicalization/mappers/demo_mappers.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/canonicalization/mappers/documents_mapper.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/canonicalization/mappers/github_activity_mapper.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/canonicalization/mappers/google_calendar_mapper.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/canonicalization/mappers/grok_mapper.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/canonicalization/mappers/messenger_mapper.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/canonicalization/models.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/canonicalization/resolver.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/cli/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/cli/__main__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/cli/assets/topos_blk_rounded.png +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/cli/assets/topos_white.png +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/cli/commands.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/cli/tray.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/config/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/config/conversation_context_llm.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/config/facts_llm.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/config/sanitization_ollama.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/config/settings.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/config/signal_extraction.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/config/uvicorn_logging.json +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/contacts/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/contacts/identity.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/control_plane_client.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/core/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/core/api_models.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/core/connection_resilience.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/core/device_helpers.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/core/errors.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/core/events.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/core/handlers/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/core/handlers/common.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/core/handlers/complexity.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/core/handlers/config.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/core/handlers/contacts_import.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/core/handlers/database_explorer.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/core/handlers/device.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/core/handlers/enrichment.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/core/handlers/enrichment_lab.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/core/handlers/filter_lab.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/core/handlers/home_chat.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/core/handlers/ingest.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/core/handlers/messages.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/core/handlers/messenger_analytics.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/core/handlers/query.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/core/handlers/registry.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/core/handlers/routines.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/core/handlers/sources.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/core/handlers/timeline_daily.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/core/handlers/tool_index.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/core/handlers/uma.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/core/logging.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/core/metrics.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/core/routine_access.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/core/startup_banner.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/core/table_layers.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/core/types.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/data_explorer_row_delete.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/data_explorer_table_prefs.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/data_explorer_tables.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/disclosure/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/disclosure/canonical_writer.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/disclosure/content_policy.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/disclosure/field_registry.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/disclosure/privacy_layer.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/disclosure/tier.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/engine/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/engine/backends/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/engine/backends/base.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/engine/backends/generative_prompts.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/engine/backends/generative_response.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/engine/backends/huggingface.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/engine/backends/ollama.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/engine/backends/openai.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/engine/backends/openai_compatible.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/engine/backends/redpill.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/engine/backends/stub.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/engine/client.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/engine/engine.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/engine/intake.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/engine/memory_utils.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/engine/model_cache.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/engine/pipeline_memory.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/engine/queue_manager.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/engine/registration.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/engine/result_formatter.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/engine/router.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/engine/scoped_token.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/engine/tasks.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/engine/transport.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/engine/usage_guard.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/engine/usage_observation.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/engine/validator.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment/catalog.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment/derived_tables.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment/job_writer.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment/jobs/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment/jobs/base.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment/jobs/canonical/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment/jobs/canonical/_batch_limits.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment/jobs/canonical/_engine_runner.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment/jobs/canonical/attention_triage_job.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment/jobs/canonical/availability_scores_job.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment/jobs/canonical/brief_fallback.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment/jobs/canonical/complexity_snapshot_job.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment/jobs/canonical/dimension_summary_job.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment/jobs/canonical/embeddings_job.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment/jobs/canonical/emo_27_job.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment/jobs/canonical/entities_job.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment/jobs/canonical/fact_extraction_job.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment/jobs/canonical/goal_extraction_job.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment/jobs/canonical/pii_redaction_job.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment/jobs/canonical/relationship_edges_job.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment/jobs/canonical/sentiment_job.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment/jobs/canonical/statistics_job.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment/jobs/canonical/timeline_job.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment/jobs/canonical/topic_clusters_job.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment/jobs/canonical/topics_job.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment/jobs/canonical/url_classification_core.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment/jobs/canonical/url_classification_enrichment_job.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment/jobs/canonical/url_classification_signal_job.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment/jobs/raw/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment/jobs/raw/attachments_job.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment/jobs/raw/language_job.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment/jobs/raw/time_normalization_job.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment/jobs/raw/tool_calls_job.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment/model_overrides.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment/models/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment/models/manager.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment/models/mvp_defaults.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment/models/registry.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment/models/versioning.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment/orchestrator.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment/processor.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment/progress_bar.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment/registry_contract.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment/signal_derivation_config.json +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment/source_overrides.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment/website_classifier.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment_lab/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment_lab/bundles.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment_lab/model_resolve.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment_lab/schema.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment_lab/service.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment_lab/store.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/enrichment_lab/worker.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/evals/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/evals/enrichment_eval.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/extensions.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/complexity/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/complexity/canonical.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/complexity/engine.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/complexity/entropy.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/complexity/focus.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/complexity/graph_metrics.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/complexity/influence.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/complexity/projection.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/complexity/snapshots.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/complexity/store.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/complexity/topic_merge.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/complexity/topics.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/complexity/topics_daily.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/complexity/volatility.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/entities/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/entities/consolidation.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/entities/declared_mappings.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/entities/dossier.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/entities/edges.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/entities/fact_materializer.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/entities/graph_enrichers.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/entities/graph_refresh.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/entities/graph_search.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/entities/linking.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/entities/maintenance.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/entities/reads.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/entities/resolver.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/facts/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/facts/extract.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/facts/llm_extract.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/facts/reads.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/facts/store.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/facts/verdicts.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/fit/evaluator.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/fit/opportunity_types.json +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/lifecycle/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/lifecycle/blackhole.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/lifecycle/blackhole_guard.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/lifecycle/blackhole_llm.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/lifecycle/blackhole_rebuild.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/lifecycle/derived_scrub.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/lifecycle/exclusions.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/lifecycle/gc.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/provenance/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/provenance/posture.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/provenance/roles.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/signal/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/signal/brief_canonical_loader.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/signal/brief_ontology.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/signal/brief_renderer.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/signal/brief_schemas.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/signal/chunking.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/signal/cluster_labels.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/signal/conversation_context.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/signal/data_health.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/signal/definitions/brief_ontologies.json +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/signal/definitions/intentions.json +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/signal/definitions/profile.json +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/signal/definitions/relationships.json +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/signal/definitions/schema/dimension_definition.schema.json +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/signal/definitions/time.json +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/signal/definitions/work.json +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/signal/dimension_briefs.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/signal/dimension_definition_loader.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/signal/dimension_profiles.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/signal/dimension_registry.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/signal/embed_context.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/signal/extraction/artifact_router.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/signal/extraction/artifact_store.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/signal/extraction/rule_extractors.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/signal/graph_sanitize.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/signal/hybrid_search.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/signal/incremental_clustering.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/signal/model_recommendations.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/signal/query_embed_cache.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/signal/schemas.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/signal/service.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/signal/signal_object_store.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/signal/source_hydration.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/signal/structured_signal.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/signal/tool_index.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/signal/topic_clustering.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/signal/typed_stores/aggregates.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/signal/typed_stores/commitments.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/signal/typed_stores/rhythm.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/signal/typed_stores/scope_materializer.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/signal/vector_codec.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/signal/vector_math.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/signal/vector_settings.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/stats/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/stats/definitions.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/stats/engine.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/stats/fold.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/stats/insights.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/timeline_projection.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/timeline_rollup.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/triage/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/triage/badges.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/triage/daily.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/triage/instruments.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/features/triage/readiness.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/filter_lab/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/filter_lab/bundles.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/filter_lab/schema.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/filter_lab/service.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/filter_lab/store.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/filter_lab/worker.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/home_chat/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/home_chat/schema.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/home_chat/store.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/hosted_pool_lease.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/canonical_pipeline.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/checkpoints/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/checkpoints/checkpoint_store.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/checkpoints/sqlite_checkpoint_store.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/inbox_crypto.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/inbox_drain.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/ingest_helpers.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/jobs.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/journal_location_fanout.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/journal_time_log_normalize.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/local_sync.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/log_preview.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/manager.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/parser.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/parsers/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/parsers/base.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/parsers/browser_parser.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/parsers/calendar_parser.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/parsers/chatgpt_conversation_flattener.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/parsers/chatgpt_parser.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/parsers/demo_file_parsers.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/parsers/documents_parser.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/parsers/github_activity_parser.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/parsers/google_calendar_parser.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/parsers/grok_parser.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/parsers/messenger_parser.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/parsers/voxterm_parser.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/progress.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/reprocess.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/sources/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/sources/base.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/sources/calendar.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/sources/chatgpt.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/sources/contact_importers.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/sources/grok.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/sources/imessage_reader.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/sources/signal_export_parser.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/sources/signal_reader.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/state_machine.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/triggers/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/triggers/file_trigger.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/triggers/sqlite_trigger.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/usage_inbox_dedupe.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/validation/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/validation/base.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/validation/schema_registry.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/ingestion/validation/schema_validator.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/lineage/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/lineage/provenance.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/lineage/tracker.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/llm_integrations_storage.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/mcp_stdio_proxy.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/observability/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/observability/alerts.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/observability/metrics.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/observability/tracing.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/openai_client.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/pipeline/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/pipeline/audit.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/pipeline/envelope.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/pipeline/job_runner.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/pipeline/job_store.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/pipeline/stages.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/pipeline/stub_enqueue.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/projections/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/projections/vector_index/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/projections/vector_index/base.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/projections/vector_index/builders.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/projections/vector_index/health_checks.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/protocol/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/query/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/query/audit.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/query/ddr.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/query/disclosure.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/query/fingerprint.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/query/game_layer.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/query/grant_cache.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/query/inference.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/query/intent.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/query/manifest.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/query/manifest_validation.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/query/minimizer.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/query/negotiation.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/query/pipeline.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/query/planner.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/query/retrieval.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/query/runtime.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/query/scope_registry.json +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/query/scope_registry_loader.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/query/session.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/query/session_utils.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/query/source_generation.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/query/truth_facts.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/query/truth_prompts.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/query/turn_classifier.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/query/types.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/query/verify.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/query/verify_modes.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/rate_limit.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/routines/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/routines/schema.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/routines/store.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/runtime_housekeeping.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/runtime_shutdown.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/runtime_update.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/sanitization/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/sanitization/nsfw_classifier.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/sanitization/ollama_transforms.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/sanitization/prewarm.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/sanitization/privacy_filter.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/scope_resolution.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/services/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/services/container.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/services/embeddings/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/services/embeddings/base.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/services/embeddings/local.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/services/embeddings/remote.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/services/interfaces.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/services/llm/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/services/llm/base.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/services/llm/openai.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/services/local.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/services/postgres.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/sources/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/sources/bundled_canonical_triples.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/sources/canonical_signal_defaults.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/sources/definitions.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/sources/install_service.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/sources/registry.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/sources/runtime_install.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/sources/scrub_attribution.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/sources/scrub_service.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/startup_banner.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/adapters/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/adapters/factory.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/adapters/fakes.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/adapters/postgres/stores.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/adapters/protocols.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/adapters/sqlite/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/adapters/sqlite/stores.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/adapters/sqlite/vector_search.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/canonical/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/canonical/activity_tables.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/canonical/ai_chat/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/canonical/ai_chat/canonicalizer.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/canonical/ai_chat/mapper.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/canonical/ai_chat/model.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/canonical/ai_chat/tables.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/canonical/calendar_contacts_tables.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/canonical/canonical_store.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/canonical/conversations_tables.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/canonical/mapping_store.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/canonical/postgres.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/client.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/connection_tuning.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/activity_events_content_v1.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/actor_role_v1.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/attention_triage_v1.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/attention_triage_v2.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/calendar_events_scheduling_v1.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/canonical_address_book_v1.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/canonical_disclosure_v1.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/canonical_nsfw_v1.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/complexity_v1.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/conversation_context_v1.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/derivation_ledger_v1.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/documents_v1.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/entity_blackhole_v1.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/entity_edges_validity_v1.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/episodes_v1.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/extraction_artifacts.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/journal_entries_duration_v1.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/journal_entries_ends_at_v1.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/journal_entries_people_v1.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/journal_entries_place_name_v1.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/journal_entries_starts_at_v1.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/pipeline_jobs_v1.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/remediation_person_model.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/scope_source_generation.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/signal_dimension_backfill_v1.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/signal_dimension_harness.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/signal_objects.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/signal_objects_period_v1.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/signal_objects_updated_by_v1.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/stage9_column_renames.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/vector_storage_v1.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/vector_storage_v2.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/vector_storage_v3.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/vector_storage_v4.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/wiki_clusters_v2.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/wiki_entities_v1.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/wiki_entity_review_v2.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/wiki_facts_v1.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/wiki_lifecycle_v1.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/wiki_mvp_phase0.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/wiki_mvp_phase1.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/wiki_mvp_phase4_messages_cutover.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/wiki_mvp_phase5_topic_clusters.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/wiki_mvp_phase6_dimension_briefs.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/wiki_mvp_query_quality.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/wiki_mvp_topic_clusters_coordination.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/wiki_stats_v1.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/migrations/wiki_timeline_v1.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/paths.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/postgres.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/schema.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/storage_breakdown.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/db/write_gate.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/enrichment/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/enrichment/canonical_enrichment_store.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/enrichment/raw_enrichment_store.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/normalized/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/normalized/normalized_store.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/oplog/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/oplog/decision.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/oplog/oplog_store.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/oplog/postgres.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/person/identity_resolver.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/projections/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/projections/index_ops_store.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/projections/vector_index_store.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/raw/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/raw/browser_flat_tables.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/raw/file_store.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/raw/raw_store.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/raw/raw_tables_manager.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/raw/sqlite_raw_store.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/security/encryption.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/signal_identity.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/source_settings.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/storage/user_identity.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/sync/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/sync/client.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/sync_handlers.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/testing/__init__.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/testing/lifespan.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/uma_contact_enrichment.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/uma_filters.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/uma_resource_id.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/uma_rpt.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/upgrades/runner.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/utils/base_object.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos/websocket_client.py +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos_node.egg-info/dependency_links.txt +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos_node.egg-info/entry_points.txt +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos_node.egg-info/requires.txt +0 -0
- {topos_node-1.3.2 → topos_node-1.3.3}/topos_node.egg-info/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "topos-node"
|
|
7
|
-
version = "1.3.
|
|
7
|
+
version = "1.3.3"
|
|
8
8
|
description = "Topos personal AI engine (FastAPI): local data, sync, control plane WebSocket client"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "Apache-2.0"
|
|
@@ -993,3 +993,43 @@ async def handle_signal_set_worn_badge(message: Dict[str, Any]) -> Optional[Dict
|
|
|
993
993
|
return {"id": req_id, "status": "ok", "payload": {"worn": worn_badge(conn)}}
|
|
994
994
|
except Exception as exc: # noqa: BLE001
|
|
995
995
|
return {"id": req_id, "status": "error", "error": str(exc)}
|
|
996
|
+
|
|
997
|
+
|
|
998
|
+
@handles("signal_pin_intent")
|
|
999
|
+
async def handle_signal_pin_intent(message: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
1000
|
+
req_id = message.get("id")
|
|
1001
|
+
if not req_id:
|
|
1002
|
+
return None
|
|
1003
|
+
payload = message.get("payload") or {}
|
|
1004
|
+
try:
|
|
1005
|
+
from ...features.triage.intents import active_intents, pin_intent
|
|
1006
|
+
|
|
1007
|
+
intent_text = str(payload.get("intent_text") or "").strip()
|
|
1008
|
+
if not intent_text:
|
|
1009
|
+
raise ValueError("intent_text is required")
|
|
1010
|
+
conn = hub.get_db_connection()
|
|
1011
|
+
pin_intent(
|
|
1012
|
+
conn,
|
|
1013
|
+
intent_text,
|
|
1014
|
+
horizon=payload.get("horizon") or "quarter",
|
|
1015
|
+
links=payload.get("links") or [],
|
|
1016
|
+
)
|
|
1017
|
+
return {"id": req_id, "status": "ok", "payload": {"intents": active_intents(conn)}}
|
|
1018
|
+
except Exception as exc: # noqa: BLE001
|
|
1019
|
+
return {"id": req_id, "status": "error", "error": str(exc)}
|
|
1020
|
+
|
|
1021
|
+
|
|
1022
|
+
@handles("signal_retire_intent")
|
|
1023
|
+
async def handle_signal_retire_intent(message: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
1024
|
+
req_id = message.get("id")
|
|
1025
|
+
if not req_id:
|
|
1026
|
+
return None
|
|
1027
|
+
payload = message.get("payload") or {}
|
|
1028
|
+
try:
|
|
1029
|
+
from ...features.triage.intents import active_intents, retire_intent
|
|
1030
|
+
|
|
1031
|
+
conn = hub.get_db_connection()
|
|
1032
|
+
retire_intent(conn, payload.get("object_key") or "")
|
|
1033
|
+
return {"id": req_id, "status": "ok", "payload": {"intents": active_intents(conn)}}
|
|
1034
|
+
except Exception as exc: # noqa: BLE001
|
|
1035
|
+
return {"id": req_id, "status": "error", "error": str(exc)}
|
|
@@ -293,11 +293,37 @@ def get_db_connection() -> Optional[sqlite3.Connection]:
|
|
|
293
293
|
except Exception as tuning_exc: # noqa: BLE001
|
|
294
294
|
logger.warning("Connection tuning skipped: %s", tuning_exc)
|
|
295
295
|
try:
|
|
296
|
-
from ..storage.db.migrations import
|
|
296
|
+
from ..storage.db.migrations import (
|
|
297
|
+
DowngradeGuardError,
|
|
298
|
+
MigrationError,
|
|
299
|
+
ensure_migrations_applied,
|
|
300
|
+
)
|
|
297
301
|
|
|
298
302
|
ensure_migrations_applied(db_conn)
|
|
303
|
+
except (MigrationError, DowngradeGuardError) as migration_exc:
|
|
304
|
+
# Shape migrations must fail loud — serving a half-migrated DB corrupts data.
|
|
305
|
+
logger.error("Schema migration failed; refusing to open database: %s", migration_exc)
|
|
306
|
+
try:
|
|
307
|
+
db_conn.close()
|
|
308
|
+
except Exception: # noqa: BLE001
|
|
309
|
+
pass
|
|
310
|
+
db_conn = None
|
|
311
|
+
_db_conn_path = None
|
|
312
|
+
raise
|
|
299
313
|
except Exception as migration_exc: # noqa: BLE001
|
|
300
|
-
|
|
314
|
+
from ..storage.db.migrations import MigrationError as _MigrationError
|
|
315
|
+
|
|
316
|
+
logger.error(
|
|
317
|
+
"Unexpected schema migration failure; refusing to open database: %s",
|
|
318
|
+
migration_exc,
|
|
319
|
+
)
|
|
320
|
+
try:
|
|
321
|
+
db_conn.close()
|
|
322
|
+
except Exception: # noqa: BLE001
|
|
323
|
+
pass
|
|
324
|
+
db_conn = None
|
|
325
|
+
_db_conn_path = None
|
|
326
|
+
raise _MigrationError(str(migration_exc)) from migration_exc
|
|
301
327
|
try:
|
|
302
328
|
from ..storage.raw.browser_flat_tables import backfill_browser_visits_from_raw_retention
|
|
303
329
|
|
|
@@ -306,6 +332,12 @@ def get_db_connection() -> Optional[sqlite3.Connection]:
|
|
|
306
332
|
logger.debug("browser_visits raw→flat backfill skipped: %s", backfill_exc)
|
|
307
333
|
return db_conn
|
|
308
334
|
except Exception as e:
|
|
335
|
+
# Shape-migration failures must propagate (fail loud). Only soft-fail
|
|
336
|
+
# unrelated connection errors so callers can treat conn=None as "no DB".
|
|
337
|
+
from ..storage.db.migrations import DowngradeGuardError, MigrationError
|
|
338
|
+
|
|
339
|
+
if isinstance(e, (MigrationError, DowngradeGuardError)):
|
|
340
|
+
raise
|
|
309
341
|
logger.warning("Failed to create database connection: %s", e)
|
|
310
342
|
db_conn = None
|
|
311
343
|
_db_conn_path = None
|
|
@@ -61,6 +61,8 @@ def attention_dashboard_data(conn: sqlite3.Connection, *, days: int = 14,
|
|
|
61
61
|
for v in verdicts:
|
|
62
62
|
v["title"] = (titles.get(v["record_id"]) or v["table"] or "")[:90]
|
|
63
63
|
|
|
64
|
+
from .intents import intent_status
|
|
65
|
+
|
|
64
66
|
intents = []
|
|
65
67
|
for row in conn.execute(
|
|
66
68
|
"SELECT payload_json, valid_from FROM signal_objects WHERE object_type='declared_intent' "
|
|
@@ -72,12 +74,18 @@ def attention_dashboard_data(conn: sqlite3.Connection, *, days: int = 14,
|
|
|
72
74
|
continue
|
|
73
75
|
# Pin instant lives on the row — surface it so pin days are plottable.
|
|
74
76
|
intent.setdefault("pinned_at", row[1])
|
|
77
|
+
intent["status"] = intent_status(intent)
|
|
75
78
|
intents.append(intent)
|
|
79
|
+
# Faded pins are not live steering, but the app shows what elapsed rather
|
|
80
|
+
# than letting an intent vanish silently.
|
|
81
|
+
faded = sorted((i for i in intents if i["status"] == "faded"),
|
|
82
|
+
key=lambda i: str(i.get("origin_timeframe") or ""), reverse=True)[:5]
|
|
76
83
|
return {
|
|
77
84
|
"days": days,
|
|
78
85
|
"verdicts": verdicts,
|
|
79
86
|
"summaries": summaries,
|
|
80
|
-
"active_intents": [i for i in intents if i
|
|
87
|
+
"active_intents": [i for i in intents if i["status"] == "active"],
|
|
88
|
+
"faded_intents": faded,
|
|
81
89
|
"badges": earned_badges(conn),
|
|
82
90
|
"current_badge": current_badge(conn),
|
|
83
91
|
"rank": rank(conn),
|
|
@@ -14,10 +14,17 @@ from __future__ import annotations
|
|
|
14
14
|
import json
|
|
15
15
|
import re
|
|
16
16
|
import sqlite3
|
|
17
|
+
from datetime import date, timedelta
|
|
17
18
|
from typing import Any, Dict, List, Optional, Tuple
|
|
18
19
|
|
|
19
20
|
from ..signal.signal_object_store import SignalObjectStore
|
|
20
21
|
|
|
22
|
+
# A pin is a bet with a shelf life. The horizon is coarse on purpose — owners
|
|
23
|
+
# think in "weeks / months / this quarter", not in target dates — and it is the
|
|
24
|
+
# only thing that decides when the pin stops steering.
|
|
25
|
+
HORIZON_DAYS = {"weeks": 21, "months": 60, "quarter": 90}
|
|
26
|
+
DEFAULT_HORIZON = "quarter"
|
|
27
|
+
|
|
21
28
|
_STOP = {"the", "a", "an", "and", "or", "of", "to", "in", "for", "on", "with",
|
|
22
29
|
"my", "our", "their", "this", "that", "is", "are", "be", "see",
|
|
23
30
|
"more", "new", "build", "collect", "evidence", "people", "things"}
|
|
@@ -28,15 +35,46 @@ def _keywords(text: str) -> List[str]:
|
|
|
28
35
|
return [w for w in words if w not in _STOP]
|
|
29
36
|
|
|
30
37
|
|
|
38
|
+
def _parse_day(value: Any) -> Optional[date]:
|
|
39
|
+
try:
|
|
40
|
+
return date.fromisoformat(str(value)[:10])
|
|
41
|
+
except (TypeError, ValueError):
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def intent_faded(intent: Dict[str, Any], *, today: Optional[date] = None) -> bool:
|
|
46
|
+
"""Has this pin's horizon elapsed since it was declared?
|
|
47
|
+
|
|
48
|
+
Read-time only — nothing is ever rewritten. An intent with no parseable
|
|
49
|
+
origin never fades (we cannot date it, so we keep steering by it).
|
|
50
|
+
"""
|
|
51
|
+
origin = _parse_day(intent.get("origin_timeframe"))
|
|
52
|
+
if origin is None:
|
|
53
|
+
return False
|
|
54
|
+
days = HORIZON_DAYS.get(str(intent.get("horizon") or DEFAULT_HORIZON),
|
|
55
|
+
HORIZON_DAYS[DEFAULT_HORIZON])
|
|
56
|
+
return origin + timedelta(days=days) < (today or date.today())
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def intent_status(intent: Dict[str, Any], *, today: Optional[date] = None) -> str:
|
|
60
|
+
"""Reported status: an explicit retire wins, otherwise the horizon decides."""
|
|
61
|
+
stored = str(intent.get("status") or "active")
|
|
62
|
+
if stored != "active":
|
|
63
|
+
return stored
|
|
64
|
+
return "faded" if intent_faded(intent, today=today) else "active"
|
|
65
|
+
|
|
66
|
+
|
|
31
67
|
def pin_intent(conn: sqlite3.Connection, intent_text: str, *,
|
|
32
|
-
horizon: str =
|
|
68
|
+
horizon: str = DEFAULT_HORIZON, links: Optional[List[str]] = None,
|
|
33
69
|
origin_timeframe: Optional[str] = None) -> Dict[str, Any]:
|
|
34
70
|
"""F1: create (or refresh) a declared_intent signal object."""
|
|
35
71
|
key = "intent:" + re.sub(r"[^a-z0-9]+", "-", intent_text.lower())[:60].strip("-")
|
|
36
72
|
payload = {
|
|
37
73
|
"intent_text": intent_text,
|
|
38
74
|
"horizon": horizon,
|
|
39
|
-
|
|
75
|
+
# The fade clock starts the moment the pin is made unless the owner
|
|
76
|
+
# backdates it — an undated pin would otherwise steer forever.
|
|
77
|
+
"origin_timeframe": origin_timeframe or date.today().isoformat(),
|
|
40
78
|
"status": "active",
|
|
41
79
|
"links": links or [],
|
|
42
80
|
"keywords": _keywords(intent_text),
|
|
@@ -49,7 +87,7 @@ def pin_intent(conn: sqlite3.Connection, intent_text: str, *,
|
|
|
49
87
|
confidence=1.0, extractor_version="owner_declared", created_by="owner")
|
|
50
88
|
|
|
51
89
|
|
|
52
|
-
def
|
|
90
|
+
def _all_intents(conn: sqlite3.Connection) -> List[Dict[str, Any]]:
|
|
53
91
|
try:
|
|
54
92
|
rows = conn.execute(
|
|
55
93
|
"SELECT object_key, payload_json FROM signal_objects "
|
|
@@ -62,12 +100,51 @@ def active_intents(conn: sqlite3.Connection) -> List[Dict[str, Any]]:
|
|
|
62
100
|
p = json.loads(pj)
|
|
63
101
|
except (TypeError, ValueError):
|
|
64
102
|
continue
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
out.append(p)
|
|
103
|
+
p["_key"] = key
|
|
104
|
+
out.append(p)
|
|
68
105
|
return out
|
|
69
106
|
|
|
70
107
|
|
|
108
|
+
def active_intents(conn: sqlite3.Connection,
|
|
109
|
+
*, today: Optional[date] = None) -> List[Dict[str, Any]]:
|
|
110
|
+
"""The live pins: neither retired by hand nor faded by their horizon."""
|
|
111
|
+
return [p for p in _all_intents(conn) if intent_status(p, today=today) == "active"]
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def faded_intents(conn: sqlite3.Connection, *, limit: int = 5,
|
|
115
|
+
today: Optional[date] = None) -> List[Dict[str, Any]]:
|
|
116
|
+
"""Pins whose horizon has elapsed, most recently declared first."""
|
|
117
|
+
out = []
|
|
118
|
+
for p in _all_intents(conn):
|
|
119
|
+
if intent_status(p, today=today) != "faded":
|
|
120
|
+
continue
|
|
121
|
+
p["status"] = "faded"
|
|
122
|
+
out.append(p)
|
|
123
|
+
out.sort(key=lambda p: str(p.get("origin_timeframe") or ""), reverse=True)
|
|
124
|
+
return out[:limit]
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def retire_intent(conn: sqlite3.Connection, object_key: str) -> Optional[Dict[str, Any]]:
|
|
128
|
+
"""Manual retire — the owner calling it before the horizon does."""
|
|
129
|
+
key = str(object_key or "").strip()
|
|
130
|
+
row = conn.execute(
|
|
131
|
+
"SELECT payload_json FROM signal_objects WHERE object_type='declared_intent' "
|
|
132
|
+
"AND object_key=? AND valid_to IS NULL", (key,)).fetchone()
|
|
133
|
+
if not row:
|
|
134
|
+
return None
|
|
135
|
+
try:
|
|
136
|
+
payload = json.loads(row[0])
|
|
137
|
+
except (TypeError, ValueError):
|
|
138
|
+
return None
|
|
139
|
+
payload["status"] = "retired"
|
|
140
|
+
payload.pop("_key", None)
|
|
141
|
+
store = SignalObjectStore(conn)
|
|
142
|
+
return store.upsert_object(
|
|
143
|
+
"intentions", "declared_intent", key, payload,
|
|
144
|
+
source_refs=[{"declared_by": "owner"}],
|
|
145
|
+
confidence=1.0, extractor_version="owner_declared", created_by="owner")
|
|
146
|
+
|
|
147
|
+
|
|
71
148
|
def intent_match(text: str, intents: List[Dict[str, Any]],
|
|
72
149
|
item_vec=None, intent_vecs=None) -> Tuple[float, Optional[str]]:
|
|
73
150
|
"""F2 semantic term: max proximity of an item to any active intent.
|
|
@@ -172,7 +172,9 @@
|
|
|
172
172
|
"signal_list_topic_clusters",
|
|
173
173
|
"signal_list_vectors",
|
|
174
174
|
"signal_owner_override_object",
|
|
175
|
+
"signal_pin_intent",
|
|
175
176
|
"signal_refresh_brief",
|
|
177
|
+
"signal_retire_intent",
|
|
176
178
|
"signal_search_vectors",
|
|
177
179
|
"signal_set_worn_badge",
|
|
178
180
|
"signal_timeline_daily",
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"""Wiki MVP storage migration runner — single registry (PLAN §4a)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import sqlite3
|
|
7
|
+
from typing import List, Optional
|
|
8
|
+
|
|
9
|
+
from .backup import (
|
|
10
|
+
InsufficientDiskForBackup,
|
|
11
|
+
backup_database_before_migrations,
|
|
12
|
+
connection_db_path,
|
|
13
|
+
)
|
|
14
|
+
# Re-export migration IDs + registry used by tests/tools.
|
|
15
|
+
from .registry import * # noqa: F403
|
|
16
|
+
from .registry import MIGRATIONS, MigrationSpec, max_migration_order # noqa: F401
|
|
17
|
+
from .canonical_disclosure_v1 import apply_canonical_disclosure_v1_up # noqa: F401
|
|
18
|
+
from .canonical_nsfw_v1 import apply_canonical_nsfw_v1_up # noqa: F401
|
|
19
|
+
from .complexity_v1 import apply_complexity_v1_up # noqa: F401
|
|
20
|
+
from .activity_events_content_v1 import apply_activity_events_content_v1_up # noqa: F401
|
|
21
|
+
from .actor_role_v1 import apply_actor_role_v1_up # noqa: F401
|
|
22
|
+
from .entity_blackhole_v1 import apply_entity_blackhole_v1_up # noqa: F401
|
|
23
|
+
from .attention_triage_v1 import apply_attention_triage_v1_up # noqa: F401
|
|
24
|
+
from .attention_triage_v2 import apply_attention_triage_v2_up # noqa: F401
|
|
25
|
+
from .documents_v1 import apply_documents_v1_up # noqa: F401
|
|
26
|
+
from .canonical_address_book_v1 import apply_canonical_address_book_v1_up # noqa: F401
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger("topos.storage.db.migrations")
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
"MigrationError",
|
|
32
|
+
"DowngradeGuardError",
|
|
33
|
+
"MigrationSpec",
|
|
34
|
+
"MIGRATIONS",
|
|
35
|
+
"apply_all_migrations",
|
|
36
|
+
"ensure_migrations_applied",
|
|
37
|
+
"pending_ledger_migrations",
|
|
38
|
+
"read_user_version",
|
|
39
|
+
"max_migration_order",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class MigrationError(RuntimeError):
|
|
44
|
+
"""Schema migration failed; the database must not be served half-migrated."""
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class DowngradeGuardError(MigrationError):
|
|
48
|
+
"""Database ``user_version`` is newer than this build knows how to open."""
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _migration_applied(conn: sqlite3.Connection, migration_id: str) -> bool:
|
|
52
|
+
try:
|
|
53
|
+
row = conn.execute(
|
|
54
|
+
"SELECT 1 FROM wiki_schema_migrations WHERE migration_id=?",
|
|
55
|
+
(migration_id,),
|
|
56
|
+
).fetchone()
|
|
57
|
+
return row is not None
|
|
58
|
+
except sqlite3.OperationalError:
|
|
59
|
+
return False
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _table_exists(conn: sqlite3.Connection, table: str) -> bool:
|
|
63
|
+
try:
|
|
64
|
+
row = conn.execute(
|
|
65
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name=?",
|
|
66
|
+
(table,),
|
|
67
|
+
).fetchone()
|
|
68
|
+
return row is not None
|
|
69
|
+
except sqlite3.OperationalError:
|
|
70
|
+
return False
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def read_user_version(conn: sqlite3.Connection) -> int:
|
|
74
|
+
row = conn.execute("PRAGMA user_version").fetchone()
|
|
75
|
+
if row is None:
|
|
76
|
+
return 0
|
|
77
|
+
return int(row[0])
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _stamp_user_version(conn: sqlite3.Connection, order: int) -> None:
|
|
81
|
+
conn.execute(f"PRAGMA user_version = {int(order)}")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _shipped_version() -> str:
|
|
85
|
+
try:
|
|
86
|
+
from ....__version__ import __version__
|
|
87
|
+
|
|
88
|
+
return str(__version__)
|
|
89
|
+
except Exception: # noqa: BLE001
|
|
90
|
+
return "unknown"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _needs_apply(conn: sqlite3.Connection, spec: MigrationSpec) -> bool:
|
|
94
|
+
if spec.always_run:
|
|
95
|
+
return True
|
|
96
|
+
if not _migration_applied(conn, spec.id):
|
|
97
|
+
return True
|
|
98
|
+
if spec.also_if_missing_table and not _table_exists(conn, spec.also_if_missing_table):
|
|
99
|
+
return True
|
|
100
|
+
return False
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def pending_ledger_migrations(conn: sqlite3.Connection) -> List[MigrationSpec]:
|
|
104
|
+
"""Ledger-guarded migrations that have not yet been recorded as applied.
|
|
105
|
+
|
|
106
|
+
Used to decide whether a pre-migration backup is required. ``always_run``
|
|
107
|
+
steps alone do not trigger a backup.
|
|
108
|
+
"""
|
|
109
|
+
pending: List[MigrationSpec] = []
|
|
110
|
+
for spec in MIGRATIONS:
|
|
111
|
+
if spec.always_run:
|
|
112
|
+
continue
|
|
113
|
+
if not _migration_applied(conn, spec.id):
|
|
114
|
+
pending.append(spec)
|
|
115
|
+
continue
|
|
116
|
+
if spec.also_if_missing_table and not _table_exists(
|
|
117
|
+
conn, spec.also_if_missing_table
|
|
118
|
+
):
|
|
119
|
+
pending.append(spec)
|
|
120
|
+
return pending
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def apply_all_migrations(conn: sqlite3.Connection) -> None:
|
|
124
|
+
"""Apply every migration unconditionally (tests / offline tools)."""
|
|
125
|
+
for spec in MIGRATIONS:
|
|
126
|
+
spec.fn(conn)
|
|
127
|
+
_stamp_user_version(conn, max_migration_order())
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def ensure_migrations_applied(
|
|
131
|
+
conn: sqlite3.Connection,
|
|
132
|
+
*,
|
|
133
|
+
skip_backup: bool = False,
|
|
134
|
+
) -> Optional[str]:
|
|
135
|
+
"""Apply pending schema migrations; fail loud on error.
|
|
136
|
+
|
|
137
|
+
Returns the backup path string when a pre-migration backup was written,
|
|
138
|
+
otherwise None. Raises ``DowngradeGuardError`` / ``MigrationError`` on
|
|
139
|
+
failure — callers must not serve a half-migrated database.
|
|
140
|
+
"""
|
|
141
|
+
max_order = max_migration_order()
|
|
142
|
+
current = read_user_version(conn)
|
|
143
|
+
if current > max_order:
|
|
144
|
+
raise DowngradeGuardError(
|
|
145
|
+
f"database was upgraded by a newer topos-node "
|
|
146
|
+
f"(PRAGMA user_version={current} > {max_order} known to this build); "
|
|
147
|
+
f"upgrade the package or restore the pre-upgrade backup under "
|
|
148
|
+
f"~/.topos/backups/"
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
pending = pending_ledger_migrations(conn)
|
|
152
|
+
backup_path: Optional[str] = None
|
|
153
|
+
|
|
154
|
+
# Fast path: fully stamped DB with nothing pending — only always_run steps.
|
|
155
|
+
if current >= max_order and not pending:
|
|
156
|
+
for spec in MIGRATIONS:
|
|
157
|
+
if not spec.always_run:
|
|
158
|
+
continue
|
|
159
|
+
try:
|
|
160
|
+
spec.fn(conn)
|
|
161
|
+
except Exception as exc: # noqa: BLE001
|
|
162
|
+
raise MigrationError(
|
|
163
|
+
f"always-run migration {spec.id!r} failed: {exc}"
|
|
164
|
+
) from exc
|
|
165
|
+
return None
|
|
166
|
+
|
|
167
|
+
if pending and not skip_backup and connection_db_path(conn) is not None:
|
|
168
|
+
try:
|
|
169
|
+
path = backup_database_before_migrations(
|
|
170
|
+
conn, shipped_version=_shipped_version()
|
|
171
|
+
)
|
|
172
|
+
if path is not None:
|
|
173
|
+
backup_path = str(path)
|
|
174
|
+
except InsufficientDiskForBackup as exc:
|
|
175
|
+
logger.error("%s", exc)
|
|
176
|
+
raise MigrationError(str(exc)) from exc
|
|
177
|
+
|
|
178
|
+
for spec in MIGRATIONS:
|
|
179
|
+
if not _needs_apply(conn, spec):
|
|
180
|
+
continue
|
|
181
|
+
try:
|
|
182
|
+
spec.fn(conn)
|
|
183
|
+
except Exception as exc: # noqa: BLE001
|
|
184
|
+
hint = f" Restore from {backup_path}." if backup_path else ""
|
|
185
|
+
raise MigrationError(
|
|
186
|
+
f"schema migration {spec.id!r} (order={spec.order}) failed: {exc}.{hint}"
|
|
187
|
+
) from exc
|
|
188
|
+
|
|
189
|
+
_stamp_user_version(conn, max_order)
|
|
190
|
+
return backup_path
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""Pre-migration SQLite backup (PLAN_NODE_RELEASE_MIGRATIONS §4a.5)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
import shutil
|
|
8
|
+
import sqlite3
|
|
9
|
+
from datetime import datetime, timezone
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import List, Optional
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger("topos.storage.db.migrations.backup")
|
|
14
|
+
|
|
15
|
+
BACKUP_RETENTION = 2
|
|
16
|
+
_BACKUP_DIR_ENV = "TOPOS_BACKUP_DIR"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class InsufficientDiskForBackup(RuntimeError):
|
|
20
|
+
"""Raised when free disk is below 2× DB size and backup cannot proceed safely."""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def backup_dir_for(db_path: Path) -> Path:
|
|
24
|
+
override = os.environ.get(_BACKUP_DIR_ENV)
|
|
25
|
+
if override:
|
|
26
|
+
return Path(override)
|
|
27
|
+
return db_path.expanduser().resolve().parent / "backups"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def connection_db_path(conn: sqlite3.Connection) -> Optional[Path]:
|
|
31
|
+
"""Return the on-disk path for the main DB, or None for :memory:/temp."""
|
|
32
|
+
try:
|
|
33
|
+
rows = conn.execute("PRAGMA database_list").fetchall()
|
|
34
|
+
except sqlite3.Error:
|
|
35
|
+
return None
|
|
36
|
+
for row in rows:
|
|
37
|
+
# row: (seq, name, file)
|
|
38
|
+
name = row[1] if not hasattr(row, "keys") else row["name"]
|
|
39
|
+
file_path = row[2] if not hasattr(row, "keys") else row["file"]
|
|
40
|
+
if str(name) != "main":
|
|
41
|
+
continue
|
|
42
|
+
if not file_path:
|
|
43
|
+
return None
|
|
44
|
+
path = Path(str(file_path))
|
|
45
|
+
if not path.is_file():
|
|
46
|
+
return None
|
|
47
|
+
return path
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _free_bytes(path: Path) -> Optional[int]:
|
|
52
|
+
try:
|
|
53
|
+
usage = shutil.disk_usage(path if path.is_dir() else path.parent)
|
|
54
|
+
return int(usage.free)
|
|
55
|
+
except OSError:
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def prune_old_backups(directory: Path, *, keep: int = BACKUP_RETENTION) -> List[Path]:
|
|
60
|
+
"""Keep the newest ``keep`` ``database-pre-v*.db`` files; delete older ones."""
|
|
61
|
+
if not directory.is_dir():
|
|
62
|
+
return []
|
|
63
|
+
candidates = sorted(
|
|
64
|
+
directory.glob("database-pre-v*.db"),
|
|
65
|
+
key=lambda p: p.stat().st_mtime,
|
|
66
|
+
reverse=True,
|
|
67
|
+
)
|
|
68
|
+
removed: List[Path] = []
|
|
69
|
+
for stale in candidates[keep:]:
|
|
70
|
+
try:
|
|
71
|
+
stale.unlink()
|
|
72
|
+
removed.append(stale)
|
|
73
|
+
except OSError as exc:
|
|
74
|
+
logger.warning("Failed to prune backup %s: %s", stale, exc)
|
|
75
|
+
return removed
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def backup_database_before_migrations(
|
|
79
|
+
conn: sqlite3.Connection,
|
|
80
|
+
*,
|
|
81
|
+
shipped_version: str,
|
|
82
|
+
keep: int = BACKUP_RETENTION,
|
|
83
|
+
) -> Optional[Path]:
|
|
84
|
+
"""Copy the live DB to ``~/.topos/backups/database-pre-v{ver}-{ts}.db``.
|
|
85
|
+
|
|
86
|
+
Returns the backup path, or None when there is no on-disk DB (in-memory /
|
|
87
|
+
test fixtures). Raises ``InsufficientDiskForBackup`` when free space is
|
|
88
|
+
below 2× the DB size (skip-with-loud-warning is the caller's choice — we
|
|
89
|
+
raise so boot can decide fail-loud vs warn).
|
|
90
|
+
"""
|
|
91
|
+
db_path = connection_db_path(conn)
|
|
92
|
+
if db_path is None:
|
|
93
|
+
return None
|
|
94
|
+
|
|
95
|
+
dest_dir = backup_dir_for(db_path)
|
|
96
|
+
dest_dir.mkdir(parents=True, exist_ok=True)
|
|
97
|
+
|
|
98
|
+
size = db_path.stat().st_size
|
|
99
|
+
free = _free_bytes(dest_dir)
|
|
100
|
+
if free is not None and size > 0 and free < 2 * size:
|
|
101
|
+
raise InsufficientDiskForBackup(
|
|
102
|
+
f"free disk {free} bytes < 2× database size {size} bytes; "
|
|
103
|
+
f"refusing to migrate without a pre-upgrade backup under {dest_dir}"
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
stamp = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
|
|
107
|
+
safe_ver = "".join(ch if ch.isalnum() or ch in "._-" else "_" for ch in shipped_version)
|
|
108
|
+
dest = dest_dir / f"database-pre-v{safe_ver}-{stamp}.db"
|
|
109
|
+
if dest.exists():
|
|
110
|
+
dest.unlink()
|
|
111
|
+
|
|
112
|
+
src = sqlite3.connect(f"file:{db_path}?mode=ro", uri=True)
|
|
113
|
+
try:
|
|
114
|
+
dst = sqlite3.connect(str(dest))
|
|
115
|
+
try:
|
|
116
|
+
with dst:
|
|
117
|
+
src.backup(dst)
|
|
118
|
+
finally:
|
|
119
|
+
dst.close()
|
|
120
|
+
finally:
|
|
121
|
+
src.close()
|
|
122
|
+
|
|
123
|
+
prune_old_backups(dest_dir, keep=keep)
|
|
124
|
+
logger.info("Pre-migration backup written to %s (%.1f MB)", dest, dest.stat().st_size / 1e6)
|
|
125
|
+
return dest
|