topos-node 1.3.5__tar.gz → 1.3.6__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.5/topos_node.egg-info → topos_node-1.3.6}/PKG-INFO +1 -1
- {topos_node-1.3.5 → topos_node-1.3.6}/pyproject.toml +1 -1
- topos_node-1.3.6/tests/test_prewarm_first_run.py +61 -0
- topos_node-1.3.6/tests/test_system_computer_name.py +113 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/__version__.py +2 -2
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/api/shell.py +7 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/api/upgrades.py +7 -1
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/core/state.py +55 -1
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/disclosure/privacy_layer.py +3 -2
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/derivation_recovery.py +5 -2
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/jobs/canonical/pii_redaction_job.py +3 -2
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/jobs/canonical/topic_clusters_job.py +16 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/signal/topic_clustering.py +2 -2
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/pipeline/job_runner.py +80 -35
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/pipeline/job_store.py +54 -2
- topos_node-1.3.6/topos/sanitization/prewarm.py +212 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/canonical/conversations_tables.py +3 -2
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/write_gate.py +19 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/upgrades/manifests.json +6 -0
- {topos_node-1.3.5 → topos_node-1.3.6/topos_node.egg-info}/PKG-INFO +1 -1
- {topos_node-1.3.5 → topos_node-1.3.6}/topos_node.egg-info/SOURCES.txt +2 -0
- topos_node-1.3.5/topos/sanitization/prewarm.py +0 -32
- {topos_node-1.3.5 → topos_node-1.3.6}/LICENSE +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/MANIFEST.in +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/README.md +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/setup.cfg +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/shared/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/shared/filtering.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/shared/schema_registry.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/tests/test_extensions.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/tests/test_llm_integrations_storage.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/tests/test_protocol_handled_types.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/analytics/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/analytics/duckdb_adapter.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/analytics/messenger_communities.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/analytics/messenger_graph.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/analytics/messenger_labels.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/analytics/profiles.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/analytics/query_engine.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/analytics/raw_queries.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/api/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/api/analytics.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/api/app_registry.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/api/backup.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/api/compute_remote.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/api/conversation_context.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/api/data_commit.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/api/data_explorer_table_prefs.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/api/database_explorer.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/api/db.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/api/device.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/api/engine_run.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/api/enrichment.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/api/enrichment_lab.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/api/facts_llm_config.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/api/filter_lab.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/api/health.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/api/home_chat.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/api/ingestion_api.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/api/ingestion_compat.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/api/ingestion_sources.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/api/intelligence_lifecycle.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/api/llm.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/api/local_mcp.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/api/messenger_analytics.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/api/privacy_disclose.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/api/query_api.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/api/runtime_bootstrap.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/api/sanitization_ollama_config.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/api/signal.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/api/signal_extraction_config.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/api/source_install.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/api/source_scrub.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/api/sources.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/api/sources_overview.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/api/sync.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/api/tool_index.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/api/ui_config.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/api/uma_data.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/api/usage.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/api/user_identity.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/app.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/auth.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/canonicalization/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/canonicalization/mappers/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/canonicalization/mappers/base.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/canonicalization/mappers/browser_activity_mapper.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/canonicalization/mappers/chatgpt_mapper.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/canonicalization/mappers/demo_mappers.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/canonicalization/mappers/documents_mapper.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/canonicalization/mappers/github_activity_mapper.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/canonicalization/mappers/google_calendar_mapper.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/canonicalization/mappers/grok_mapper.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/canonicalization/mappers/messenger_mapper.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/canonicalization/models.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/canonicalization/resolver.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/cli/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/cli/__main__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/cli/assets/topos_blk_rounded.png +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/cli/assets/topos_white.png +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/cli/commands.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/cli/reprocess_cmd.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/cli/tray.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/config/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/config/conversation_context_llm.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/config/facts_llm.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/config/model_packs.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/config/sanitization_ollama.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/config/settings.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/config/signal_extraction.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/config/uvicorn_logging.json +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/contacts/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/contacts/identity.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/control_plane_client.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/core/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/core/api_models.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/core/connection_resilience.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/core/device_helpers.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/core/errors.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/core/events.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/core/handlers/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/core/handlers/common.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/core/handlers/complexity.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/core/handlers/config.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/core/handlers/contacts_import.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/core/handlers/database_explorer.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/core/handlers/device.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/core/handlers/enrichment.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/core/handlers/enrichment_lab.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/core/handlers/filter_lab.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/core/handlers/graph_query.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/core/handlers/home_chat.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/core/handlers/ingest.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/core/handlers/messages.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/core/handlers/messenger_analytics.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/core/handlers/query.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/core/handlers/registry.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/core/handlers/routines.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/core/handlers/signal_features.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/core/handlers/sources.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/core/handlers/timeline_daily.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/core/handlers/tool_index.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/core/handlers/uma.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/core/logging.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/core/metrics.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/core/routine_access.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/core/startup_banner.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/core/table_layers.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/core/types.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/data_explorer_row_delete.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/data_explorer_table_prefs.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/data_explorer_tables.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/disclosure/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/disclosure/canonical_writer.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/disclosure/content_policy.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/disclosure/field_registry.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/disclosure/tier.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/engine/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/engine/backends/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/engine/backends/base.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/engine/backends/generative_prompts.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/engine/backends/generative_response.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/engine/backends/huggingface.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/engine/backends/ollama.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/engine/backends/openai.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/engine/backends/openai_compatible.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/engine/backends/redpill.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/engine/backends/stub.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/engine/client.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/engine/engine.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/engine/intake.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/engine/memory_utils.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/engine/model_cache.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/engine/pipeline_memory.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/engine/queue_manager.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/engine/registration.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/engine/result_formatter.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/engine/router.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/engine/scoped_token.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/engine/tasks.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/engine/transport.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/engine/usage_guard.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/engine/usage_observation.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/engine/validator.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/catalog.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/derived_tables.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/job_writer.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/jobs/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/jobs/base.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/jobs/canonical/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/jobs/canonical/_batch_limits.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/jobs/canonical/_engine_runner.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/jobs/canonical/attention_triage_job.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/jobs/canonical/availability_scores_job.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/jobs/canonical/brief_fallback.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/jobs/canonical/complexity_snapshot_job.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/jobs/canonical/dimension_summary_job.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/jobs/canonical/embeddings_job.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/jobs/canonical/emo_27_job.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/jobs/canonical/entities_job.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/jobs/canonical/fact_extraction_job.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/jobs/canonical/goal_extraction_job.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/jobs/canonical/relationship_edges_job.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/jobs/canonical/sentiment_job.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/jobs/canonical/statistics_job.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/jobs/canonical/timeline_job.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/jobs/canonical/topics_job.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/jobs/canonical/url_classification_core.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/jobs/canonical/url_classification_enrichment_job.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/jobs/canonical/url_classification_signal_job.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/jobs/raw/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/jobs/raw/attachments_job.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/jobs/raw/language_job.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/jobs/raw/time_normalization_job.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/jobs/raw/tool_calls_job.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/model_overrides.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/models/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/models/manager.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/models/mvp_defaults.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/models/registry.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/models/versioning.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/orchestrator.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/pipeline_activity.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/processor.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/progress_bar.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/registry_contract.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/signal_derivation_config.json +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/source_overrides.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment/website_classifier.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment_lab/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment_lab/bundles.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment_lab/model_resolve.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment_lab/schema.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment_lab/service.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment_lab/store.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/enrichment_lab/worker.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/evals/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/evals/enrichment_eval.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/extensions.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/complexity/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/complexity/canonical.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/complexity/engine.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/complexity/entropy.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/complexity/focus.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/complexity/graph_metrics.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/complexity/influence.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/complexity/projection.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/complexity/snapshots.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/complexity/store.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/complexity/topic_merge.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/complexity/topics.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/complexity/topics_daily.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/complexity/volatility.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/entities/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/entities/affinity.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/entities/affinity_owner.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/entities/affinity_quality.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/entities/consolidation.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/entities/context_vectors.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/entities/cypher.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/entities/declared_mappings.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/entities/dossier.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/entities/edges.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/entities/fact_materializer.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/entities/graph_enrichers.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/entities/graph_refresh.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/entities/graph_search.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/entities/linking.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/entities/maintenance.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/entities/reads.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/entities/resolver.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/facts/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/facts/extract.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/facts/llm_extract.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/facts/reads.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/facts/store.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/facts/verdicts.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/fit/evaluator.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/fit/opportunity_types.json +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/lifecycle/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/lifecycle/blackhole.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/lifecycle/blackhole_guard.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/lifecycle/blackhole_llm.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/lifecycle/blackhole_rebuild.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/lifecycle/derived_scrub.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/lifecycle/exclusions.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/lifecycle/gc.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/provenance/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/provenance/posture.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/provenance/roles.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/signal/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/signal/brief_canonical_loader.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/signal/brief_ontology.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/signal/brief_renderer.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/signal/brief_schemas.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/signal/chunking.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/signal/cluster_labels.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/signal/conversation_context.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/signal/data_health.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/signal/definitions/brief_ontologies.json +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/signal/definitions/intentions.json +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/signal/definitions/profile.json +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/signal/definitions/relationships.json +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/signal/definitions/schema/dimension_definition.schema.json +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/signal/definitions/time.json +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/signal/definitions/work.json +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/signal/derived_spec.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/signal/dimension_briefs.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/signal/dimension_definition_loader.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/signal/dimension_profiles.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/signal/dimension_registry.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/signal/embed_context.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/signal/extraction/artifact_router.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/signal/extraction/artifact_store.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/signal/extraction/rule_extractors.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/signal/graph_sanitize.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/signal/hybrid_search.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/signal/incremental_clustering.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/signal/model_recommendations.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/signal/query_embed_cache.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/signal/schemas.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/signal/service.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/signal/signal_object_store.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/signal/source_hydration.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/signal/structured_signal.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/signal/tool_index.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/signal/typed_stores/aggregates.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/signal/typed_stores/commitments.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/signal/typed_stores/rhythm.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/signal/typed_stores/scope_materializer.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/signal/vector_codec.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/signal/vector_math.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/signal/vector_settings.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/stats/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/stats/definitions.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/stats/engine.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/stats/fold.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/stats/insights.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/timeline_projection.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/timeline_rollup.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/triage/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/triage/badges.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/triage/daily.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/triage/dashboard.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/triage/instruments.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/triage/intents.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/features/triage/readiness.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/filter_lab/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/filter_lab/bundles.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/filter_lab/schema.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/filter_lab/service.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/filter_lab/store.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/filter_lab/worker.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/home_chat/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/home_chat/schema.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/home_chat/store.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/hosted_pool_lease.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/canonical_pipeline.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/checkpoints/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/checkpoints/checkpoint_store.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/checkpoints/sqlite_checkpoint_store.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/inbox_crypto.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/inbox_drain.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/ingest_helpers.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/jobs.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/journal_location_fanout.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/journal_time_log_normalize.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/local_sync.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/log_preview.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/manager.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/parser.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/parsers/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/parsers/base.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/parsers/browser_parser.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/parsers/calendar_parser.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/parsers/chatgpt_conversation_flattener.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/parsers/chatgpt_parser.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/parsers/demo_file_parsers.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/parsers/documents_parser.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/parsers/github_activity_parser.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/parsers/google_calendar_parser.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/parsers/grok_parser.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/parsers/messenger_parser.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/parsers/voxterm_parser.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/progress.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/reprocess.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/sources/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/sources/base.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/sources/calendar.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/sources/chatgpt.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/sources/contact_importers.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/sources/grok.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/sources/imessage_reader.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/sources/signal_export_parser.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/sources/signal_reader.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/state_machine.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/triggers/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/triggers/file_trigger.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/triggers/sqlite_trigger.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/usage_inbox_dedupe.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/validation/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/validation/base.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/validation/schema_registry.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/ingestion/validation/schema_validator.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/lineage/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/lineage/provenance.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/lineage/tracker.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/llm_integrations_storage.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/mcp_stdio_proxy.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/observability/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/observability/alerts.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/observability/metrics.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/observability/tracing.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/openai_client.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/pipeline/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/pipeline/audit.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/pipeline/envelope.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/pipeline/stages.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/pipeline/stub_enqueue.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/projections/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/projections/vector_index/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/projections/vector_index/base.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/projections/vector_index/builders.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/projections/vector_index/health_checks.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/protocol/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/protocol/handled_message_types.json +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/query/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/query/audit.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/query/cohort_resolvers.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/query/ddr.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/query/disclosure.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/query/fingerprint.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/query/game_layer.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/query/grant_cache.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/query/inference.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/query/intent.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/query/manifest.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/query/manifest_validation.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/query/minimizer.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/query/negotiation.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/query/pipeline.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/query/planner.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/query/retrieval.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/query/runtime.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/query/scope_registry.json +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/query/scope_registry_loader.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/query/session.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/query/session_utils.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/query/source_generation.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/query/truth_facts.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/query/truth_prompts.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/query/turn_classifier.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/query/types.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/query/verify.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/query/verify_modes.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/rate_limit.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/routines/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/routines/schema.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/routines/store.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/runtime_housekeeping.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/runtime_shutdown.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/runtime_update.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/sanitization/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/sanitization/nsfw_classifier.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/sanitization/ollama_transforms.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/sanitization/privacy_filter.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/scope_resolution.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/services/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/services/container.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/services/embeddings/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/services/embeddings/base.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/services/embeddings/local.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/services/embeddings/remote.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/services/interfaces.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/services/llm/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/services/llm/base.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/services/llm/openai.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/services/local.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/services/postgres.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/sources/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/sources/bundled_canonical_triples.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/sources/canonical_signal_defaults.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/sources/definitions.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/sources/install_service.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/sources/registry.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/sources/runtime_install.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/sources/scrub_attribution.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/sources/scrub_service.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/startup_banner.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/adapters/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/adapters/factory.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/adapters/fakes.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/adapters/postgres/stores.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/adapters/protocols.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/adapters/sqlite/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/adapters/sqlite/stores.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/adapters/sqlite/vector_search.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/canonical/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/canonical/activity_tables.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/canonical/ai_chat/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/canonical/ai_chat/canonicalizer.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/canonical/ai_chat/mapper.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/canonical/ai_chat/model.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/canonical/ai_chat/tables.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/canonical/calendar_contacts_tables.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/canonical/canonical_store.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/canonical/mapping_store.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/canonical/postgres.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/client.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/connection_tuning.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/activity_events_content_v1.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/actor_role_v1.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/affinity_pair_labels_v1.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/attention_triage_v1.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/attention_triage_v2.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/backup.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/calendar_events_scheduling_v1.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/canonical_address_book_v1.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/canonical_disclosure_v1.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/canonical_nsfw_v1.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/complexity_v1.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/conversation_context_v1.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/derivation_ledger_v1.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/documents_v1.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/enrichment_spec_version_v1.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/entity_affinity_v1.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/entity_blackhole_v1.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/entity_context_vectors_v1.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/entity_context_vectors_v2.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/entity_edges_validity_v1.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/entity_mentions_authored_v1.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/episodes_v1.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/extraction_artifacts.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/journal_entries_duration_v1.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/journal_entries_ends_at_v1.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/journal_entries_people_v1.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/journal_entries_place_name_v1.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/journal_entries_starts_at_v1.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/message_emotions_role_backfill_v1.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/message_emotions_role_v1.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/pipeline_jobs_v1.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/registry.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/registry_checksums.json +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/remediation_person_model.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/scope_source_generation.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/signal_dimension_backfill_v1.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/signal_dimension_harness.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/signal_objects.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/signal_objects_period_v1.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/signal_objects_updated_by_v1.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/signal_summaries_drop_v1.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/stage9_column_renames.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/vector_storage_v1.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/vector_storage_v2.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/vector_storage_v3.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/vector_storage_v4.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/wiki_clusters_v2.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/wiki_entities_v1.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/wiki_entity_review_v2.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/wiki_facts_v1.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/wiki_lifecycle_v1.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/wiki_mvp_phase0.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/wiki_mvp_phase1.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/wiki_mvp_phase4_messages_cutover.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/wiki_mvp_phase5_topic_clusters.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/wiki_mvp_phase6_dimension_briefs.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/wiki_mvp_query_quality.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/wiki_mvp_topic_clusters_coordination.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/wiki_stats_v1.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/migrations/wiki_timeline_v1.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/paths.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/postgres.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/schema.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/db/storage_breakdown.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/enrichment/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/enrichment/canonical_enrichment_store.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/enrichment/raw_enrichment_store.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/normalized/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/normalized/normalized_store.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/oplog/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/oplog/decision.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/oplog/oplog_store.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/oplog/postgres.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/person/identity_resolver.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/projections/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/projections/index_ops_store.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/projections/vector_index_store.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/raw/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/raw/browser_flat_tables.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/raw/file_store.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/raw/raw_store.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/raw/raw_tables_manager.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/raw/sqlite_raw_store.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/security/encryption.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/signal_identity.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/source_settings.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/storage/user_identity.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/sync/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/sync/client.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/sync_handlers.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/testing/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/testing/lifespan.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/uma_contact_enrichment.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/uma_filters.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/uma_resource_id.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/uma_rpt.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/upgrades/__init__.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/upgrades/runner.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/utils/base_object.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos/websocket_client.py +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos_node.egg-info/dependency_links.txt +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos_node.egg-info/entry_points.txt +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/topos_node.egg-info/requires.txt +0 -0
- {topos_node-1.3.5 → topos_node-1.3.6}/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.6"
|
|
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"
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""first_run decides whether the UI says "downloading" or "preparing".
|
|
2
|
+
|
|
3
|
+
Getting it wrong is a lie in the copy: a node that reports first_run=False shows
|
|
4
|
+
"preparing its language models… should only take a moment" while it fetches 2.9GB.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
|
|
11
|
+
import pytest
|
|
12
|
+
|
|
13
|
+
from topos.sanitization import prewarm
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.fixture
|
|
17
|
+
def cache_root(tmp_path, monkeypatch):
|
|
18
|
+
monkeypatch.setattr(prewarm, "_hf_cache_root", lambda: str(tmp_path))
|
|
19
|
+
return tmp_path
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _write_blobs(cache_root, repo_id: str, names: list[str]) -> None:
|
|
23
|
+
blobs = os.path.join(prewarm._repo_dir(str(cache_root), repo_id), "blobs")
|
|
24
|
+
os.makedirs(blobs, exist_ok=True)
|
|
25
|
+
for name in names:
|
|
26
|
+
open(os.path.join(blobs, name), "w").close()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
REPO = prewarm.PRIVACY_FILTER_REPO
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_absent_repo_is_not_present(cache_root):
|
|
33
|
+
assert prewarm._repo_present(REPO) is False
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_started_but_empty_is_not_present(cache_root):
|
|
37
|
+
"""The first instant of a first download — the directory exists, nothing is in it."""
|
|
38
|
+
_write_blobs(cache_root, REPO, [])
|
|
39
|
+
assert prewarm._repo_present(REPO) is False
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_interrupted_download_is_not_present(cache_root):
|
|
43
|
+
"""The case that made this a bug: a killed first run leaves .incomplete blobs
|
|
44
|
+
behind, and directory-existence alone would call that done."""
|
|
45
|
+
_write_blobs(cache_root, REPO, ["abc123.incomplete"])
|
|
46
|
+
assert prewarm._repo_present(REPO) is False
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_partially_resumed_download_is_not_present(cache_root):
|
|
50
|
+
_write_blobs(cache_root, REPO, ["abc123", "def456.incomplete"])
|
|
51
|
+
assert prewarm._repo_present(REPO) is False
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_complete_download_is_present(cache_root):
|
|
55
|
+
_write_blobs(cache_root, REPO, ["abc123", "def456"])
|
|
56
|
+
assert prewarm._repo_present(REPO) is True
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_no_cache_root_is_not_present(monkeypatch):
|
|
60
|
+
monkeypatch.setattr(prewarm, "_hf_cache_root", lambda: None)
|
|
61
|
+
assert prewarm._repo_present(REPO) is False
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""The machine name a Mac owner recognises, reported alongside the hostname.
|
|
2
|
+
|
|
3
|
+
`platform.node()` returns the network hostname, which on macOS is either a
|
|
4
|
+
mangled form of the computer name ("jonnys-macbook-pro.local") or something
|
|
5
|
+
meaningless from DHCP ("mac.lan" on the machine this was written on). The
|
|
6
|
+
frontend names a new self-hosted Topos after this, so a bad answer here shows up
|
|
7
|
+
as a Topos called "mac.lan".
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import subprocess
|
|
13
|
+
|
|
14
|
+
import pytest
|
|
15
|
+
|
|
16
|
+
from topos.core import state
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@pytest.fixture(autouse=True)
|
|
20
|
+
def _clear_cache():
|
|
21
|
+
state._computer_name_cache = state._UNRESOLVED
|
|
22
|
+
yield
|
|
23
|
+
state._computer_name_cache = state._UNRESOLVED
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _fake_run(stdout: str):
|
|
27
|
+
def run(_cmd, **_kwargs):
|
|
28
|
+
return subprocess.CompletedProcess(args=_cmd, returncode=0, stdout=stdout, stderr="")
|
|
29
|
+
|
|
30
|
+
return run
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_reports_the_computer_name_on_macos(monkeypatch):
|
|
34
|
+
monkeypatch.setattr(state.platform, "system", lambda: "Darwin")
|
|
35
|
+
monkeypatch.setattr(state.subprocess, "run", _fake_run("Jonny's MacBook Pro\n"))
|
|
36
|
+
|
|
37
|
+
assert state._macos_computer_name() == "Jonny's MacBook Pro"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_absent_off_macos(monkeypatch):
|
|
41
|
+
monkeypatch.setattr(state.platform, "system", lambda: "Linux")
|
|
42
|
+
|
|
43
|
+
def explode(*_a, **_k): # pragma: no cover - must never be reached
|
|
44
|
+
raise AssertionError("scutil is macOS-only and must not be spawned elsewhere")
|
|
45
|
+
|
|
46
|
+
monkeypatch.setattr(state.subprocess, "run", explode)
|
|
47
|
+
assert state._macos_computer_name() is None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@pytest.mark.parametrize("stdout", ["", " \n"])
|
|
51
|
+
def test_blank_output_is_no_name(monkeypatch, stdout):
|
|
52
|
+
monkeypatch.setattr(state.platform, "system", lambda: "Darwin")
|
|
53
|
+
monkeypatch.setattr(state.subprocess, "run", _fake_run(stdout))
|
|
54
|
+
|
|
55
|
+
assert state._macos_computer_name() is None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_failure_is_swallowed_and_never_retried(monkeypatch):
|
|
59
|
+
"""A nicer label is never worth a startup failure — nor a stall on every request.
|
|
60
|
+
|
|
61
|
+
get_system_info runs inside async handlers, so an uncached spawn blocks the
|
|
62
|
+
event loop for the full timeout. On a Mac with no ComputerName that would be
|
|
63
|
+
a permanent per-request stall, so a miss is cached exactly like a hit and the
|
|
64
|
+
node falls back to the hostname for the rest of its life.
|
|
65
|
+
"""
|
|
66
|
+
monkeypatch.setattr(state.platform, "system", lambda: "Darwin")
|
|
67
|
+
calls = {"n": 0}
|
|
68
|
+
|
|
69
|
+
def always_times_out(_cmd, **_kwargs):
|
|
70
|
+
calls["n"] += 1
|
|
71
|
+
raise subprocess.TimeoutExpired(cmd=_cmd, timeout=2.0)
|
|
72
|
+
|
|
73
|
+
monkeypatch.setattr(state.subprocess, "run", always_times_out)
|
|
74
|
+
|
|
75
|
+
assert state._macos_computer_name() is None
|
|
76
|
+
assert state._macos_computer_name() is None
|
|
77
|
+
assert state._macos_computer_name() is None
|
|
78
|
+
assert calls["n"] == 1
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def test_non_macos_never_spawns_twice(monkeypatch):
|
|
82
|
+
monkeypatch.setattr(state.platform, "system", lambda: "Linux")
|
|
83
|
+
assert state._macos_computer_name() is None
|
|
84
|
+
# Cached, so a Linux node does not re-check platform on every request either.
|
|
85
|
+
assert state._computer_name_cache is None
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def test_spawns_once_per_process(monkeypatch):
|
|
89
|
+
"""get_device_info calls get_system_info twice, and the CP hits it on every connect."""
|
|
90
|
+
monkeypatch.setattr(state.platform, "system", lambda: "Darwin")
|
|
91
|
+
calls = {"n": 0}
|
|
92
|
+
|
|
93
|
+
def counting(_cmd, **_kwargs):
|
|
94
|
+
calls["n"] += 1
|
|
95
|
+
return subprocess.CompletedProcess(args=_cmd, returncode=0, stdout="q4\n", stderr="")
|
|
96
|
+
|
|
97
|
+
monkeypatch.setattr(state.subprocess, "run", counting)
|
|
98
|
+
|
|
99
|
+
for _ in range(5):
|
|
100
|
+
state._macos_computer_name()
|
|
101
|
+
|
|
102
|
+
assert calls["n"] == 1
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def test_system_info_carries_computer_name_beside_hostname(monkeypatch):
|
|
106
|
+
"""The dict is free-form and the control plane forwards it verbatim, so this
|
|
107
|
+
key is the entire wire contract — it lands as system_info.computer_name."""
|
|
108
|
+
monkeypatch.setattr(state, "_macos_computer_name", lambda: "Jonny's MacBook Pro")
|
|
109
|
+
|
|
110
|
+
info = state.get_system_info()
|
|
111
|
+
|
|
112
|
+
assert info["computer_name"] == "Jonny's MacBook Pro"
|
|
113
|
+
assert "hostname" in info
|
|
@@ -44,6 +44,8 @@ def _client_is_local(request: Request) -> bool:
|
|
|
44
44
|
|
|
45
45
|
@router.get("/status")
|
|
46
46
|
async def shell_status() -> dict:
|
|
47
|
+
from ..sanitization.prewarm import prewarm_status
|
|
48
|
+
|
|
47
49
|
info = get_update_info()
|
|
48
50
|
log_file = get_log_file_path()
|
|
49
51
|
return {
|
|
@@ -59,6 +61,11 @@ async def shell_status() -> dict:
|
|
|
59
61
|
"applying": _apply_state["applying"],
|
|
60
62
|
"last_result": _apply_state["last_result"],
|
|
61
63
|
},
|
|
64
|
+
# Additive: existing shells decode a fixed set of keys and ignore this.
|
|
65
|
+
# The tray cannot show it until the Swift ShellStatus struct adds the
|
|
66
|
+
# field (its CodingKeys are explicit), which needs its own signed build —
|
|
67
|
+
# shipping the data first means that release is a display change only.
|
|
68
|
+
"models": prewarm_status(),
|
|
62
69
|
}
|
|
63
70
|
|
|
64
71
|
|
|
@@ -19,11 +19,17 @@ router = APIRouter()
|
|
|
19
19
|
def _status_payload() -> Dict[str, Any]:
|
|
20
20
|
from ..core.state import get_db_connection
|
|
21
21
|
from ..features.entities.graph_refresh import status as refresh_status
|
|
22
|
+
from ..sanitization.prewarm import prewarm_status
|
|
22
23
|
from ..upgrades.runner import runner_status
|
|
23
24
|
|
|
24
25
|
conn = get_db_connection()
|
|
25
26
|
upgrade = runner_status(conn) if conn is not None else {"enabled": False, "error": "no database"}
|
|
26
|
-
|
|
27
|
+
# `models` rides this existing read because the control plane already
|
|
28
|
+
# proxies it verbatim (engine_config.py returns `result.get("payload")`),
|
|
29
|
+
# `get_upgrade_status` is already allowlisted, and it is in the node's
|
|
30
|
+
# fast-inbound set — so it answers in milliseconds even while the prewarm
|
|
31
|
+
# thread is saturated downloading. No control-plane change, no CP deploy.
|
|
32
|
+
return {"upgrade": upgrade, "graph_refresh": refresh_status(), "models": prewarm_status()}
|
|
27
33
|
|
|
28
34
|
|
|
29
35
|
@router.get("/v1/upgrade/status", dependencies=[Depends(require_api_key)])
|
|
@@ -5,6 +5,7 @@ import asyncio
|
|
|
5
5
|
import os
|
|
6
6
|
import platform
|
|
7
7
|
import shutil
|
|
8
|
+
import subprocess
|
|
8
9
|
import sqlite3
|
|
9
10
|
import sys
|
|
10
11
|
import threading
|
|
@@ -719,8 +720,10 @@ def _migrate_legacy_browser_plugin_app_ids(conn: sqlite3.Connection) -> int:
|
|
|
719
720
|
f"UPDATE {UMA_ACCESS_REQUESTS_TABLE} SET app_id = ? WHERE app_id = ?",
|
|
720
721
|
(BROWSER_HISTORY_PLUGIN_APP_ID, LEGACY_BROWSER_PLUGIN_APP_ID),
|
|
721
722
|
)
|
|
723
|
+
# Commit even for 0 rows: the UPDATE opened an implicit transaction
|
|
724
|
+
# either way, and this runs at startup on the owner connection.
|
|
725
|
+
conn.commit()
|
|
722
726
|
if cursor.rowcount:
|
|
723
|
-
conn.commit()
|
|
724
727
|
logger.info(
|
|
725
728
|
"Migrated %s uma_access_requests rows: %s → %s",
|
|
726
729
|
cursor.rowcount,
|
|
@@ -1082,6 +1085,53 @@ def get_total_memory_bytes() -> Optional[int]:
|
|
|
1082
1085
|
return None
|
|
1083
1086
|
|
|
1084
1087
|
|
|
1088
|
+
# Resolved at most once per process.
|
|
1089
|
+
#
|
|
1090
|
+
# get_device_info calls get_system_info twice per request (services/local.py,
|
|
1091
|
+
# for engine_name and for the system dict), the control plane hits it on every
|
|
1092
|
+
# websocket connect, and these are async handlers — an uncached subprocess.run
|
|
1093
|
+
# blocks the event loop for its full timeout every time. Failures are cached
|
|
1094
|
+
# too, deliberately: a Mac with ComputerName unset, or a slow configd, would
|
|
1095
|
+
# otherwise stall EVERY request by up to the timeout, forever. One stall is a
|
|
1096
|
+
# blip; a permanent per-request stall is an outage. The cost of caching a miss
|
|
1097
|
+
# is a Topos named after the hostname instead, which is exactly the fallback
|
|
1098
|
+
# that already exists.
|
|
1099
|
+
_UNRESOLVED = object()
|
|
1100
|
+
_computer_name_cache: Any = _UNRESOLVED
|
|
1101
|
+
|
|
1102
|
+
|
|
1103
|
+
def _macos_computer_name() -> Optional[str]:
|
|
1104
|
+
"""The name a Mac owner actually recognises — "Jonny's MacBook Pro".
|
|
1105
|
+
|
|
1106
|
+
`platform.node()` returns the network hostname, which on macOS is a mangled
|
|
1107
|
+
form of the same thing ("jonnys-macbook-pro.local") or, on many networks,
|
|
1108
|
+
something meaningless from DHCP. A Topos named after it reads like a server.
|
|
1109
|
+
|
|
1110
|
+
Read via scutil rather than a new dependency: pyobjc/SystemConfiguration
|
|
1111
|
+
would be a wheel added to every install for one string. Absolute path,
|
|
1112
|
+
bounded timeout, and every failure swallowed — a nicer label is never worth
|
|
1113
|
+
risking node startup. (Measured at 8ms, but a 1s bound proved tight enough to
|
|
1114
|
+
trip under load, and the first boot is the one call that has to answer.)
|
|
1115
|
+
"""
|
|
1116
|
+
global _computer_name_cache
|
|
1117
|
+
if _computer_name_cache is not _UNRESOLVED:
|
|
1118
|
+
return _computer_name_cache
|
|
1119
|
+
if platform.system() != "Darwin":
|
|
1120
|
+
_computer_name_cache = None
|
|
1121
|
+
return None
|
|
1122
|
+
try:
|
|
1123
|
+
out = subprocess.run(
|
|
1124
|
+
["/usr/sbin/scutil", "--get", "ComputerName"],
|
|
1125
|
+
capture_output=True,
|
|
1126
|
+
text=True,
|
|
1127
|
+
timeout=2.0,
|
|
1128
|
+
)
|
|
1129
|
+
_computer_name_cache = (out.stdout or "").strip() or None
|
|
1130
|
+
except Exception: # noqa: BLE001
|
|
1131
|
+
_computer_name_cache = None
|
|
1132
|
+
return _computer_name_cache
|
|
1133
|
+
|
|
1134
|
+
|
|
1085
1135
|
def get_system_info() -> Dict[str, Any]:
|
|
1086
1136
|
disk_total = None
|
|
1087
1137
|
disk_free = None
|
|
@@ -1097,6 +1147,10 @@ def get_system_info() -> Dict[str, Any]:
|
|
|
1097
1147
|
disk_free = None
|
|
1098
1148
|
return {
|
|
1099
1149
|
"hostname": platform.node() or None,
|
|
1150
|
+
# Free-form dict (api_models.py types `system` as Dict[str, Any]) and the
|
|
1151
|
+
# control plane forwards it verbatim, so this reaches fleet status as
|
|
1152
|
+
# system_info.computer_name with no CP change at all.
|
|
1153
|
+
"computer_name": _macos_computer_name(),
|
|
1100
1154
|
"os": platform.system() or None,
|
|
1101
1155
|
"os_version": platform.version() or None,
|
|
1102
1156
|
"platform": platform.platform() or None,
|
|
@@ -349,8 +349,9 @@ async def run_privacy_disclosure_layer(
|
|
|
349
349
|
):
|
|
350
350
|
nsfw_tagged += 1
|
|
351
351
|
|
|
352
|
-
|
|
353
|
-
|
|
352
|
+
# Commit unconditionally: 0-row updates still opened an implicit
|
|
353
|
+
# transaction, which must not outlive this call.
|
|
354
|
+
conn.commit()
|
|
354
355
|
|
|
355
356
|
duration_ms = int((time.perf_counter() - started) * 1000)
|
|
356
357
|
logger.debug(
|
|
@@ -172,8 +172,11 @@ def clear_derivation_retry(
|
|
|
172
172
|
(key,),
|
|
173
173
|
)
|
|
174
174
|
changed = int(cur.rowcount or 0)
|
|
175
|
-
|
|
176
|
-
|
|
175
|
+
# Commit even when 0 rows matched: the UPDATE opened an implicit
|
|
176
|
+
# transaction regardless, and leaving it open makes the next
|
|
177
|
+
# BEGIN IMMEDIATE on this connection fail — which broke every
|
|
178
|
+
# topic_clusters batch (this runs after each successful job).
|
|
179
|
+
commit_connection(conn)
|
|
177
180
|
if changed:
|
|
178
181
|
logger.info(
|
|
179
182
|
"[DERIVE:RETRY] cleared derivation debt job=%s batch=%s",
|
|
@@ -123,6 +123,7 @@ class PiiRedactionJob(BaseEnrichmentJob):
|
|
|
123
123
|
if progress_callback:
|
|
124
124
|
progress_callback(idx + 1, total)
|
|
125
125
|
|
|
126
|
-
|
|
127
|
-
|
|
126
|
+
# Commit unconditionally: a no-change pass may still have opened an
|
|
127
|
+
# implicit transaction, which must not outlive this job.
|
|
128
|
+
conn.commit()
|
|
128
129
|
return [{"records_updated": updated}] if updated else []
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import asyncio
|
|
3
4
|
import logging
|
|
4
5
|
import os
|
|
5
6
|
from typing import Any, Callable, Dict, List, Optional
|
|
@@ -138,6 +139,21 @@ class TopicClusterJob(BaseEnrichmentJob):
|
|
|
138
139
|
self,
|
|
139
140
|
canonical_messages: List[Dict[str, Any]],
|
|
140
141
|
progress_callback: Optional[Callable[[int, int], None]] = None,
|
|
142
|
+
) -> List[Dict[str, Any]]:
|
|
143
|
+
# The clustering path blocks for seconds at a time (k-means, the
|
|
144
|
+
# label-pool wait, persist under the write gate); on the event loop
|
|
145
|
+
# that starves every coroutine including the control-plane keepalive.
|
|
146
|
+
# get_db_connection is thread-local, so the worker thread gets its own
|
|
147
|
+
# handle — passing the loop thread's connection across would reinstate
|
|
148
|
+
# the cross-thread sharing behind the 2026-07-30 corruption.
|
|
149
|
+
return await asyncio.to_thread(
|
|
150
|
+
self._enrich_sync, canonical_messages, progress_callback
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
def _enrich_sync(
|
|
154
|
+
self,
|
|
155
|
+
canonical_messages: List[Dict[str, Any]],
|
|
156
|
+
progress_callback: Optional[Callable[[int, int], None]] = None,
|
|
141
157
|
) -> List[Dict[str, Any]]:
|
|
142
158
|
conn = get_db_connection()
|
|
143
159
|
if conn is None:
|
|
@@ -1250,11 +1250,11 @@ def recompute_topic_clusters(
|
|
|
1250
1250
|
)
|
|
1251
1251
|
|
|
1252
1252
|
_strip_member_vectors(clusters)
|
|
1253
|
-
from ...storage.db.write_gate import sqlite_retry_busy, with_db_write
|
|
1253
|
+
from ...storage.db.write_gate import begin_immediate, sqlite_retry_busy, with_db_write
|
|
1254
1254
|
|
|
1255
1255
|
with with_db_write():
|
|
1256
1256
|
try:
|
|
1257
|
-
|
|
1257
|
+
begin_immediate(conn)
|
|
1258
1258
|
_clear_all_topic_clusters(conn)
|
|
1259
1259
|
persist_result = persist_topic_clusters(
|
|
1260
1260
|
conn,
|
|
@@ -11,6 +11,7 @@ import uuid
|
|
|
11
11
|
from typing import Any, Awaitable, Callable, Dict, Optional
|
|
12
12
|
|
|
13
13
|
from .job_store import (
|
|
14
|
+
claim_matching_queued_jobs,
|
|
14
15
|
claim_next_job,
|
|
15
16
|
complete_job,
|
|
16
17
|
fail_job,
|
|
@@ -144,17 +145,23 @@ async def _execute_topic_consolidation(payload: Dict[str, Any]) -> Dict[str, Any
|
|
|
144
145
|
# what this executor exists to avoid.
|
|
145
146
|
return {"status": "error", "error": "derivation in flight; will retry"}
|
|
146
147
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
148
|
+
def _run() -> Dict[str, Any]:
|
|
149
|
+
# Fetched INSIDE the worker thread: get_db_connection is thread-local,
|
|
150
|
+
# and handing the loop thread's connection across threads is the
|
|
151
|
+
# sharing that caused the 2026-07-30 transaction corruption.
|
|
152
|
+
own = get_db_connection()
|
|
153
|
+
if own is None:
|
|
154
|
+
return {"status": "error", "error": "no database connection"}
|
|
155
|
+
return recompute_topic_clusters(
|
|
156
|
+
own,
|
|
157
|
+
source_ids=list(_resolved_topic_cluster_source_ids()),
|
|
158
|
+
sync_batch_id=None,
|
|
159
|
+
min_records=3,
|
|
160
|
+
)
|
|
150
161
|
|
|
151
|
-
result = await asyncio.to_thread(
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
source_ids=list(_resolved_topic_cluster_source_ids()),
|
|
155
|
-
sync_batch_id=None,
|
|
156
|
-
min_records=3,
|
|
157
|
-
)
|
|
162
|
+
result = await asyncio.to_thread(_run)
|
|
163
|
+
if str(result.get("status") or "") == "error":
|
|
164
|
+
return result
|
|
158
165
|
logger.info("topic consolidation complete: %s", result)
|
|
159
166
|
return {"status": "ok", "result": result}
|
|
160
167
|
|
|
@@ -167,13 +174,43 @@ EXECUTORS: Dict[str, ExecutorFn] = {
|
|
|
167
174
|
}
|
|
168
175
|
|
|
169
176
|
|
|
177
|
+
#: Upper bound on inbox jobs merged into one derive batch. A CP backlog after
|
|
178
|
+
#: downtime arrives as one job per delivery (usually one record each); merging
|
|
179
|
+
#: them runs the per-batch jobs (dimension briefs, topic work) once instead of
|
|
180
|
+
#: once per delivery. Leftovers beyond the cap stay queued for the next cycle.
|
|
181
|
+
_COALESCE_MAX_JOBS = 100
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _coalesce_inbox_jobs(
|
|
185
|
+
conn, job: Dict[str, Any], payload: Dict[str, Any]
|
|
186
|
+
) -> tuple[list[Dict[str, Any]], Dict[str, Any]]:
|
|
187
|
+
"""Merge queued inbox jobs for the same source into this job's batch."""
|
|
188
|
+
siblings = claim_matching_queued_jobs(
|
|
189
|
+
conn,
|
|
190
|
+
lease_owner=_lease_owner,
|
|
191
|
+
kind=str(job["kind"]),
|
|
192
|
+
source_id=job.get("source_id"),
|
|
193
|
+
limit=_COALESCE_MAX_JOBS,
|
|
194
|
+
)
|
|
195
|
+
if not siblings:
|
|
196
|
+
return [job], payload
|
|
197
|
+
merged_records = list(payload.get("canonical_records") or [])
|
|
198
|
+
for sibling in siblings:
|
|
199
|
+
merged_records.extend((sibling.get("payload") or {}).get("canonical_records") or [])
|
|
200
|
+
merged_payload = {**payload, "canonical_records": merged_records}
|
|
201
|
+
logger.info(
|
|
202
|
+
"coalesced %d inbox jobs into one derive batch: source=%s records=%d",
|
|
203
|
+
len(siblings) + 1,
|
|
204
|
+
job.get("source_id"),
|
|
205
|
+
len(merged_records),
|
|
206
|
+
)
|
|
207
|
+
return [job, *siblings], merged_payload
|
|
208
|
+
|
|
209
|
+
|
|
170
210
|
async def process_job(conn, job: Dict[str, Any]) -> None:
|
|
171
|
-
"""Run one claimed job to completion."""
|
|
211
|
+
"""Run one claimed job (plus any coalesced siblings) to completion."""
|
|
172
212
|
job_id = str(job["job_id"])
|
|
173
213
|
kind = str(job["kind"])
|
|
174
|
-
write_id = job.get("write_id")
|
|
175
|
-
source_id = job.get("source_id")
|
|
176
|
-
sync_batch_id = job.get("sync_batch_id")
|
|
177
214
|
|
|
178
215
|
executor = EXECUTORS.get(kind)
|
|
179
216
|
if executor is None:
|
|
@@ -184,38 +221,46 @@ async def process_job(conn, job: Dict[str, Any]) -> None:
|
|
|
184
221
|
update_job_progress(conn, job_id, progress)
|
|
185
222
|
|
|
186
223
|
payload = dict(job.get("payload") or {})
|
|
224
|
+
jobs = [job]
|
|
225
|
+
if kind == "inbox_deferred_enrichment":
|
|
226
|
+
jobs, payload = _coalesce_inbox_jobs(conn, job, payload)
|
|
187
227
|
if kind == "enrichment_process_source":
|
|
188
228
|
payload["_progress_updater"] = _progress_updater
|
|
189
229
|
|
|
190
230
|
try:
|
|
191
231
|
result = await executor(payload)
|
|
192
232
|
if str(result.get("status") or "ok") == "error":
|
|
193
|
-
|
|
194
|
-
|
|
233
|
+
error = str(result.get("error") or result.get("message") or "failed")
|
|
234
|
+
for entry in jobs:
|
|
235
|
+
fail_job(conn, str(entry["job_id"]), error=error)
|
|
236
|
+
update_job_progress(conn, str(entry["job_id"]), {"status": "failed", "result": result})
|
|
195
237
|
return
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
conn,
|
|
199
|
-
|
|
200
|
-
{
|
|
201
|
-
"status": "completed",
|
|
202
|
-
"messages_processed": result.get("messages_processed", 0),
|
|
203
|
-
"records_created": result.get("records_created", {}),
|
|
204
|
-
"errors": result.get("errors", []),
|
|
205
|
-
},
|
|
206
|
-
)
|
|
207
|
-
if kind == "inbox_deferred_enrichment" and write_id:
|
|
208
|
-
record_derivation_completion(
|
|
238
|
+
for entry in jobs:
|
|
239
|
+
entry_id = str(entry["job_id"])
|
|
240
|
+
complete_job(conn, entry_id, detail=result)
|
|
241
|
+
update_job_progress(
|
|
209
242
|
conn,
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
243
|
+
entry_id,
|
|
244
|
+
{
|
|
245
|
+
"status": "completed",
|
|
246
|
+
"messages_processed": result.get("messages_processed", 0),
|
|
247
|
+
"records_created": result.get("records_created", {}),
|
|
248
|
+
"errors": result.get("errors", []),
|
|
249
|
+
},
|
|
214
250
|
)
|
|
251
|
+
if kind == "inbox_deferred_enrichment" and entry.get("write_id"):
|
|
252
|
+
record_derivation_completion(
|
|
253
|
+
conn,
|
|
254
|
+
write_id=str(entry["write_id"]),
|
|
255
|
+
job_id=entry_id,
|
|
256
|
+
source_id=entry.get("source_id"),
|
|
257
|
+
sync_batch_id=entry.get("sync_batch_id"),
|
|
258
|
+
)
|
|
215
259
|
except Exception as exc: # noqa: BLE001
|
|
216
260
|
logger.warning("pipeline job failed job_id=%s kind=%s: %s", job_id, kind, exc, exc_info=exc)
|
|
217
|
-
|
|
218
|
-
|
|
261
|
+
for entry in jobs:
|
|
262
|
+
fail_job(conn, str(entry["job_id"]), error=str(exc))
|
|
263
|
+
update_job_progress(conn, str(entry["job_id"]), {"status": "failed", "error": str(exc)})
|
|
219
264
|
|
|
220
265
|
|
|
221
266
|
#: Idle poll interval. Every tick claims against SQLite, so this is also how
|
|
@@ -8,7 +8,7 @@ import uuid
|
|
|
8
8
|
from datetime import datetime, timedelta, timezone
|
|
9
9
|
from typing import Any, Dict, List, Optional
|
|
10
10
|
|
|
11
|
-
from ..storage.db.write_gate import commit_connection, sqlite_retry_busy, with_db_write
|
|
11
|
+
from ..storage.db.write_gate import begin_immediate, commit_connection, sqlite_retry_busy, with_db_write
|
|
12
12
|
|
|
13
13
|
JOB_STATUSES = frozenset({"queued", "running", "done", "failed"})
|
|
14
14
|
DEFAULT_LEASE_SECONDS = 300
|
|
@@ -145,7 +145,7 @@ def claim_next_job(
|
|
|
145
145
|
expires = (datetime.now(timezone.utc) + timedelta(seconds=lease_seconds)).isoformat()
|
|
146
146
|
|
|
147
147
|
with with_db_write():
|
|
148
|
-
|
|
148
|
+
begin_immediate(conn)
|
|
149
149
|
try:
|
|
150
150
|
query = "SELECT job_id FROM pipeline_jobs WHERE status='queued'"
|
|
151
151
|
params: list[Any] = []
|
|
@@ -179,6 +179,58 @@ def claim_next_job(
|
|
|
179
179
|
return get_job(conn, job_id)
|
|
180
180
|
|
|
181
181
|
|
|
182
|
+
def claim_matching_queued_jobs(
|
|
183
|
+
conn: sqlite3.Connection,
|
|
184
|
+
*,
|
|
185
|
+
lease_owner: str,
|
|
186
|
+
kind: str,
|
|
187
|
+
source_id: Optional[str],
|
|
188
|
+
lease_seconds: int = DEFAULT_LEASE_SECONDS,
|
|
189
|
+
limit: int = 100,
|
|
190
|
+
) -> List[Dict[str, Any]]:
|
|
191
|
+
"""Claim every queued job of ``kind`` for ``source_id``, oldest first.
|
|
192
|
+
|
|
193
|
+
Lets the worker coalesce a backlog — e.g. 40 one-record inbox deliveries —
|
|
194
|
+
into a single derive batch so the per-batch jobs (dimension briefs, topic
|
|
195
|
+
work) run once instead of once per delivery.
|
|
196
|
+
"""
|
|
197
|
+
ensure_pipeline_jobs_schema(conn)
|
|
198
|
+
expires = (datetime.now(timezone.utc) + timedelta(seconds=lease_seconds)).isoformat()
|
|
199
|
+
claimed_ids: List[str] = []
|
|
200
|
+
|
|
201
|
+
with with_db_write():
|
|
202
|
+
begin_immediate(conn)
|
|
203
|
+
try:
|
|
204
|
+
query = "SELECT job_id FROM pipeline_jobs WHERE status='queued' AND kind=?"
|
|
205
|
+
params: list[Any] = [kind]
|
|
206
|
+
if source_id is None:
|
|
207
|
+
query += " AND source_id IS NULL"
|
|
208
|
+
else:
|
|
209
|
+
query += " AND source_id=?"
|
|
210
|
+
params.append(source_id)
|
|
211
|
+
query += " ORDER BY created_at ASC LIMIT ?"
|
|
212
|
+
params.append(int(limit))
|
|
213
|
+
rows = conn.execute(query, tuple(params)).fetchall()
|
|
214
|
+
for row in rows:
|
|
215
|
+
updated = conn.execute(
|
|
216
|
+
"""
|
|
217
|
+
UPDATE pipeline_jobs
|
|
218
|
+
SET status='running', lease_owner=?, lease_expires_at=?,
|
|
219
|
+
started_at=COALESCE(started_at, datetime('now')),
|
|
220
|
+
updated_at=datetime('now')
|
|
221
|
+
WHERE job_id=? AND status='queued'
|
|
222
|
+
""",
|
|
223
|
+
(lease_owner, expires, str(row[0])),
|
|
224
|
+
)
|
|
225
|
+
if int(updated.rowcount or 0):
|
|
226
|
+
claimed_ids.append(str(row[0]))
|
|
227
|
+
sqlite_retry_busy(conn.commit)
|
|
228
|
+
except Exception:
|
|
229
|
+
conn.execute("ROLLBACK")
|
|
230
|
+
raise
|
|
231
|
+
return [job for job in (get_job(conn, jid) for jid in claimed_ids) if job is not None]
|
|
232
|
+
|
|
233
|
+
|
|
182
234
|
def complete_job(
|
|
183
235
|
conn: sqlite3.Connection,
|
|
184
236
|
job_id: str,
|