superlocalmemory 4.1.13 → 4.1.15
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.
- package/.claude-plugin/marketplace.json +2 -2
- package/CHANGELOG.md +103 -0
- package/README.md +3 -3
- package/package.json +1 -8
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/CLAUDE.md +3 -3
- package/plugin/agents/slm-governance-advisor.md +1 -1
- package/plugin/agents/slm-loop-runner.md +1 -1
- package/plugin/agents/slm-memory-advisor.md +1 -1
- package/plugin/agents/slm-optimize-advisor.md +1 -1
- package/plugin/requirements.txt +1 -1
- package/plugin/skills/slm-cache/SKILL.md +1 -1
- package/plugin/skills/slm-compress/SKILL.md +1 -1
- package/plugin/skills/slm-governance/SKILL.md +1 -1
- package/plugin/skills/slm-graph/SKILL.md +1 -1
- package/plugin/skills/slm-loop/SKILL.md +1 -1
- package/plugin/skills/slm-mesh/SKILL.md +1 -1
- package/plugin/skills/slm-profile/SKILL.md +1 -1
- package/plugin/skills/slm-recall/SKILL.md +1 -1
- package/plugin/skills/slm-remember/SKILL.md +1 -1
- package/plugin/skills/slm-scope/SKILL.md +1 -1
- package/plugin/skills/slm-session/SKILL.md +1 -1
- package/plugin/skills/slm-status/SKILL.md +1 -1
- package/plugin-src/agents/slm-memory-advisor.md +1 -1
- package/plugin-src/agents/slm-optimize-advisor.md +1 -1
- package/plugin-src/rules/AGENTS.md +1 -1
- package/plugin-src/skills/slm-cache/SKILL.md +1 -1
- package/plugin-src/skills/slm-compress/SKILL.md +1 -1
- package/plugin-src/skills/slm-governance/SKILL.md +1 -1
- package/plugin-src/skills/slm-graph/SKILL.md +1 -1
- package/plugin-src/skills/slm-loop/SKILL.md +1 -1
- package/plugin-src/skills/slm-mesh/SKILL.md +1 -1
- package/plugin-src/skills/slm-profile/SKILL.md +1 -1
- package/plugin-src/skills/slm-recall/SKILL.md +1 -1
- package/plugin-src/skills/slm-remember/SKILL.md +1 -1
- package/plugin-src/skills/slm-scope/SKILL.md +1 -1
- package/plugin-src/skills/slm-session/SKILL.md +1 -1
- package/plugin-src/skills/slm-status/SKILL.md +1 -1
- package/scripts/postinstall.js +71 -2
- package/pyproject.toml +0 -250
- package/src/superlocalmemory/__init__.py +0 -82
- package/src/superlocalmemory/access/__init__.py +0 -3
- package/src/superlocalmemory/access/rbac.py +0 -575
- package/src/superlocalmemory/attribution/__init__.py +0 -9
- package/src/superlocalmemory/attribution/signer.py +0 -173
- package/src/superlocalmemory/attribution/watermark.py +0 -189
- package/src/superlocalmemory/brain/__init__.py +0 -5
- package/src/superlocalmemory/brain/truth.py +0 -418
- package/src/superlocalmemory/cli/__init__.py +0 -5
- package/src/superlocalmemory/cli/__main__.py +0 -17
- package/src/superlocalmemory/cli/_lazy_init.py +0 -115
- package/src/superlocalmemory/cli/cache_cmd.py +0 -198
- package/src/superlocalmemory/cli/commands.py +0 -4710
- package/src/superlocalmemory/cli/compress_cmd.py +0 -151
- package/src/superlocalmemory/cli/context_commands.py +0 -193
- package/src/superlocalmemory/cli/daemon.py +0 -909
- package/src/superlocalmemory/cli/db_migrate.py +0 -150
- package/src/superlocalmemory/cli/diagnostics_cmd.py +0 -101
- package/src/superlocalmemory/cli/escape_hatch.py +0 -220
- package/src/superlocalmemory/cli/evidence_cmd.py +0 -103
- package/src/superlocalmemory/cli/gdpr_cmd.py +0 -792
- package/src/superlocalmemory/cli/gdpr_io.py +0 -109
- package/src/superlocalmemory/cli/help_cmd.py +0 -197
- package/src/superlocalmemory/cli/host_upgrades.py +0 -189
- package/src/superlocalmemory/cli/ingest_cmd.py +0 -327
- package/src/superlocalmemory/cli/json_output.py +0 -81
- package/src/superlocalmemory/cli/loop_cmd.py +0 -187
- package/src/superlocalmemory/cli/main.py +0 -1145
- package/src/superlocalmemory/cli/mesh_cmd.py +0 -38
- package/src/superlocalmemory/cli/migrate_cmd.py +0 -55
- package/src/superlocalmemory/cli/ops_cmd.py +0 -281
- package/src/superlocalmemory/cli/optimize_cmd.py +0 -179
- package/src/superlocalmemory/cli/optimize_constants.py +0 -31
- package/src/superlocalmemory/cli/pending_store.py +0 -296
- package/src/superlocalmemory/cli/proxy_cmd.py +0 -108
- package/src/superlocalmemory/cli/scale_engine_cmd.py +0 -56
- package/src/superlocalmemory/cli/service_installer.py +0 -373
- package/src/superlocalmemory/cli/setup_wizard.py +0 -1162
- package/src/superlocalmemory/cli/summary_cmd.py +0 -215
- package/src/superlocalmemory/cli/version_banner.py +0 -202
- package/src/superlocalmemory/cli/wizard_v3426_options.py +0 -129
- package/src/superlocalmemory/code_graph/__init__.py +0 -46
- package/src/superlocalmemory/code_graph/blast_radius.py +0 -177
- package/src/superlocalmemory/code_graph/bridge/__init__.py +0 -36
- package/src/superlocalmemory/code_graph/bridge/entity_resolver.py +0 -490
- package/src/superlocalmemory/code_graph/bridge/event_listeners.py +0 -206
- package/src/superlocalmemory/code_graph/bridge/fact_enricher.py +0 -159
- package/src/superlocalmemory/code_graph/bridge/hebbian_linker.py +0 -170
- package/src/superlocalmemory/code_graph/bridge/maintenance.py +0 -220
- package/src/superlocalmemory/code_graph/bridge/temporal_checker.py +0 -152
- package/src/superlocalmemory/code_graph/changes.py +0 -363
- package/src/superlocalmemory/code_graph/communities.py +0 -299
- package/src/superlocalmemory/code_graph/config.py +0 -154
- package/src/superlocalmemory/code_graph/database.py +0 -526
- package/src/superlocalmemory/code_graph/extractors/__init__.py +0 -95
- package/src/superlocalmemory/code_graph/extractors/python.py +0 -413
- package/src/superlocalmemory/code_graph/extractors/typescript.py +0 -556
- package/src/superlocalmemory/code_graph/flows.py +0 -350
- package/src/superlocalmemory/code_graph/git_hooks.py +0 -226
- package/src/superlocalmemory/code_graph/graph_engine.py +0 -295
- package/src/superlocalmemory/code_graph/graph_store.py +0 -335
- package/src/superlocalmemory/code_graph/incremental.py +0 -200
- package/src/superlocalmemory/code_graph/models.py +0 -130
- package/src/superlocalmemory/code_graph/parser.py +0 -687
- package/src/superlocalmemory/code_graph/resolver.py +0 -321
- package/src/superlocalmemory/code_graph/search.py +0 -460
- package/src/superlocalmemory/code_graph/service.py +0 -95
- package/src/superlocalmemory/code_graph/watcher.py +0 -207
- package/src/superlocalmemory/compliance/__init__.py +0 -0
- package/src/superlocalmemory/compliance/abac.py +0 -204
- package/src/superlocalmemory/compliance/audit.py +0 -385
- package/src/superlocalmemory/compliance/eu_ai_act.py +0 -101
- package/src/superlocalmemory/compliance/gdpr.py +0 -1479
- package/src/superlocalmemory/compliance/lifecycle.py +0 -158
- package/src/superlocalmemory/compliance/retention.py +0 -415
- package/src/superlocalmemory/compliance/scheduler.py +0 -217
- package/src/superlocalmemory/contracts/__init__.py +0 -1
- package/src/superlocalmemory/contracts/schemas/agent-experience-v1.schema.json +0 -92
- package/src/superlocalmemory/contracts/schemas/agent-integration-contract-v2.schema.json +0 -46
- package/src/superlocalmemory/contracts/schemas/cognitive-turn-receipt-v1.schema.json +0 -59
- package/src/superlocalmemory/contracts/v402.py +0 -62
- package/src/superlocalmemory/core/__init__.py +0 -0
- package/src/superlocalmemory/core/actor_context.py +0 -166
- package/src/superlocalmemory/core/admission.py +0 -769
- package/src/superlocalmemory/core/backend_orchestrator.py +0 -637
- package/src/superlocalmemory/core/block_hygiene.py +0 -147
- package/src/superlocalmemory/core/community_summary.py +0 -267
- package/src/superlocalmemory/core/component_healer.py +0 -144
- package/src/superlocalmemory/core/component_registry.py +0 -514
- package/src/superlocalmemory/core/config.py +0 -2204
- package/src/superlocalmemory/core/consolidation_engine.py +0 -983
- package/src/superlocalmemory/core/context_cache.py +0 -574
- package/src/superlocalmemory/core/derivation_lineage.py +0 -246
- package/src/superlocalmemory/core/embedding_worker.py +0 -208
- package/src/superlocalmemory/core/embeddings.py +0 -1052
- package/src/superlocalmemory/core/engine.py +0 -1395
- package/src/superlocalmemory/core/engine_capabilities.py +0 -24
- package/src/superlocalmemory/core/engine_ingestion.py +0 -983
- package/src/superlocalmemory/core/engine_lock.py +0 -75
- package/src/superlocalmemory/core/engine_wiring.py +0 -776
- package/src/superlocalmemory/core/entity_community.py +0 -178
- package/src/superlocalmemory/core/error_envelope.py +0 -60
- package/src/superlocalmemory/core/evidence_bundle.py +0 -528
- package/src/superlocalmemory/core/fact_consolidator.py +0 -812
- package/src/superlocalmemory/core/file_lock.py +0 -92
- package/src/superlocalmemory/core/graph_analyzer.py +0 -456
- package/src/superlocalmemory/core/graph_metrics.py +0 -597
- package/src/superlocalmemory/core/graph_pruner.py +0 -939
- package/src/superlocalmemory/core/health_monitor.py +0 -338
- package/src/superlocalmemory/core/hooks.py +0 -65
- package/src/superlocalmemory/core/ingest_gate.py +0 -133
- package/src/superlocalmemory/core/ingest_policy.py +0 -38
- package/src/superlocalmemory/core/ingestion_command.py +0 -1042
- package/src/superlocalmemory/core/injection.py +0 -434
- package/src/superlocalmemory/core/install_detector.py +0 -131
- package/src/superlocalmemory/core/key_expander.py +0 -138
- package/src/superlocalmemory/core/lifecycle_state.py +0 -153
- package/src/superlocalmemory/core/maintenance.py +0 -777
- package/src/superlocalmemory/core/maintenance_scheduler.py +0 -507
- package/src/superlocalmemory/core/materialization_control.py +0 -20
- package/src/superlocalmemory/core/mcp_embedder_proxy.py +0 -89
- package/src/superlocalmemory/core/memory_health.py +0 -266
- package/src/superlocalmemory/core/mode_capability.py +0 -111
- package/src/superlocalmemory/core/modes.py +0 -168
- package/src/superlocalmemory/core/mutations.py +0 -688
- package/src/superlocalmemory/core/ollama_embedder.py +0 -266
- package/src/superlocalmemory/core/ollama_validator.py +0 -315
- package/src/superlocalmemory/core/operation_policy.py +0 -92
- package/src/superlocalmemory/core/operation_policy_registry.py +0 -542
- package/src/superlocalmemory/core/operation_request.py +0 -127
- package/src/superlocalmemory/core/ops_remediation.py +0 -542
- package/src/superlocalmemory/core/pii.py +0 -105
- package/src/superlocalmemory/core/platform_utils.py +0 -138
- package/src/superlocalmemory/core/profiles.py +0 -234
- package/src/superlocalmemory/core/progressive_abstraction.py +0 -208
- package/src/superlocalmemory/core/projection_drain.py +0 -380
- package/src/superlocalmemory/core/queue_consumer.py +0 -168
- package/src/superlocalmemory/core/ram_lock.py +0 -160
- package/src/superlocalmemory/core/rate_limit.py +0 -151
- package/src/superlocalmemory/core/recall_gate.py +0 -95
- package/src/superlocalmemory/core/recall_pipeline.py +0 -1337
- package/src/superlocalmemory/core/recall_queue.py +0 -377
- package/src/superlocalmemory/core/recall_worker.py +0 -414
- package/src/superlocalmemory/core/registry.py +0 -121
- package/src/superlocalmemory/core/remember_admission.py +0 -161
- package/src/superlocalmemory/core/remember_runtime.py +0 -1190
- package/src/superlocalmemory/core/remote_mode.py +0 -214
- package/src/superlocalmemory/core/reranker_worker.py +0 -338
- package/src/superlocalmemory/core/safe_fs.py +0 -108
- package/src/superlocalmemory/core/scale_autopromote.py +0 -196
- package/src/superlocalmemory/core/scale_engine.py +0 -915
- package/src/superlocalmemory/core/score_contract.py +0 -82
- package/src/superlocalmemory/core/security_primitives.py +0 -672
- package/src/superlocalmemory/core/session_identity.py +0 -98
- package/src/superlocalmemory/core/shadow_router.py +0 -319
- package/src/superlocalmemory/core/slm_disabled.py +0 -85
- package/src/superlocalmemory/core/status_contract.py +0 -108
- package/src/superlocalmemory/core/store_pipeline.py +0 -1404
- package/src/superlocalmemory/core/summarizer.py +0 -200
- package/src/superlocalmemory/core/tier_manager.py +0 -461
- package/src/superlocalmemory/core/topic_signature.py +0 -156
- package/src/superlocalmemory/core/transactions/__init__.py +0 -78
- package/src/superlocalmemory/core/transactions/concrete_owners.py +0 -604
- package/src/superlocalmemory/core/transactions/erasure.py +0 -825
- package/src/superlocalmemory/core/transactions/manifest.py +0 -255
- package/src/superlocalmemory/core/transactions/manifest_key.py +0 -155
- package/src/superlocalmemory/core/transactions/obligations.py +0 -272
- package/src/superlocalmemory/core/transactions/owners.py +0 -114
- package/src/superlocalmemory/core/transactions/reconciler.py +0 -285
- package/src/superlocalmemory/core/transactions/service.py +0 -330
- package/src/superlocalmemory/core/worker_pool.py +0 -377
- package/src/superlocalmemory/core/working_memory.py +0 -288
- package/src/superlocalmemory/dynamics/__init__.py +0 -0
- package/src/superlocalmemory/dynamics/activation_guided_quantization.py +0 -374
- package/src/superlocalmemory/dynamics/eap_scheduler.py +0 -294
- package/src/superlocalmemory/dynamics/ebbinghaus_langevin_coupling.py +0 -171
- package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +0 -227
- package/src/superlocalmemory/encoding/__init__.py +0 -0
- package/src/superlocalmemory/encoding/auto_linker.py +0 -308
- package/src/superlocalmemory/encoding/cognitive_consolidator.py +0 -899
- package/src/superlocalmemory/encoding/consolidator.py +0 -472
- package/src/superlocalmemory/encoding/context_generator.py +0 -175
- package/src/superlocalmemory/encoding/emotional.py +0 -189
- package/src/superlocalmemory/encoding/entity_reflexion.py +0 -200
- package/src/superlocalmemory/encoding/entity_resolver.py +0 -687
- package/src/superlocalmemory/encoding/entropy_gate.py +0 -101
- package/src/superlocalmemory/encoding/fact_extractor.py +0 -877
- package/src/superlocalmemory/encoding/foresight.py +0 -93
- package/src/superlocalmemory/encoding/graph_builder.py +0 -346
- package/src/superlocalmemory/encoding/observation_builder.py +0 -177
- package/src/superlocalmemory/encoding/prospective_markers.py +0 -262
- package/src/superlocalmemory/encoding/scene_builder.py +0 -410
- package/src/superlocalmemory/encoding/signal_inference.py +0 -90
- package/src/superlocalmemory/encoding/temporal_parser.py +0 -432
- package/src/superlocalmemory/encoding/temporal_validator.py +0 -572
- package/src/superlocalmemory/encoding/type_router.py +0 -237
- package/src/superlocalmemory/evaluation/__init__.py +0 -13
- package/src/superlocalmemory/evaluation/calibration.py +0 -308
- package/src/superlocalmemory/evolution/__init__.py +0 -29
- package/src/superlocalmemory/evolution/blind_verifier.py +0 -122
- package/src/superlocalmemory/evolution/budget.py +0 -356
- package/src/superlocalmemory/evolution/evolution_store.py +0 -619
- package/src/superlocalmemory/evolution/llm_dispatch.py +0 -559
- package/src/superlocalmemory/evolution/model_selection.py +0 -175
- package/src/superlocalmemory/evolution/mutation_generator.py +0 -226
- package/src/superlocalmemory/evolution/skill_activator.py +0 -270
- package/src/superlocalmemory/evolution/skill_evolver.py +0 -928
- package/src/superlocalmemory/evolution/triggers.py +0 -376
- package/src/superlocalmemory/evolution/types.py +0 -114
- package/src/superlocalmemory/graph/__init__.py +0 -9
- package/src/superlocalmemory/graph/cozo_adjacency.py +0 -122
- package/src/superlocalmemory/graph/cozo_backend.py +0 -751
- package/src/superlocalmemory/hooks/__init__.py +0 -3
- package/src/superlocalmemory/hooks/_outcome_common.py +0 -523
- package/src/superlocalmemory/hooks/adapter_base.py +0 -347
- package/src/superlocalmemory/hooks/antigravity_adapter.py +0 -171
- package/src/superlocalmemory/hooks/auto_capture.py +0 -133
- package/src/superlocalmemory/hooks/auto_invoker.py +0 -521
- package/src/superlocalmemory/hooks/auto_parameterize.py +0 -147
- package/src/superlocalmemory/hooks/auto_recall.py +0 -191
- package/src/superlocalmemory/hooks/auto_recall_hook.py +0 -251
- package/src/superlocalmemory/hooks/before_web_hook.py +0 -131
- package/src/superlocalmemory/hooks/claude_code_hooks.py +0 -637
- package/src/superlocalmemory/hooks/codex_assets.py +0 -251
- package/src/superlocalmemory/hooks/codex_hooks.py +0 -186
- package/src/superlocalmemory/hooks/context_payload.py +0 -311
- package/src/superlocalmemory/hooks/copilot_adapter.py +0 -208
- package/src/superlocalmemory/hooks/cross_platform_connector.py +0 -90
- package/src/superlocalmemory/hooks/cursor_adapter.py +0 -173
- package/src/superlocalmemory/hooks/hook_daemon.py +0 -295
- package/src/superlocalmemory/hooks/hook_handlers.py +0 -822
- package/src/superlocalmemory/hooks/ide_connector.py +0 -246
- package/src/superlocalmemory/hooks/memory_protocol.py +0 -158
- package/src/superlocalmemory/hooks/portable_kit.py +0 -755
- package/src/superlocalmemory/hooks/post_tool_async_hook.py +0 -183
- package/src/superlocalmemory/hooks/post_tool_outcome_hook.py +0 -351
- package/src/superlocalmemory/hooks/prewarm_auth.py +0 -187
- package/src/superlocalmemory/hooks/rules_engine.py +0 -99
- package/src/superlocalmemory/hooks/session_registry.py +0 -330
- package/src/superlocalmemory/hooks/stop_outcome_hook.py +0 -138
- package/src/superlocalmemory/hooks/sync_loop.py +0 -114
- package/src/superlocalmemory/hooks/topic_shift_hook.py +0 -302
- package/src/superlocalmemory/hooks/user_prompt_hook.py +0 -131
- package/src/superlocalmemory/hooks/user_prompt_rehash_hook.py +0 -210
- package/src/superlocalmemory/infra/__init__.py +0 -3
- package/src/superlocalmemory/infra/auth_middleware.py +0 -145
- package/src/superlocalmemory/infra/backup.py +0 -974
- package/src/superlocalmemory/infra/backup_obligations.py +0 -423
- package/src/superlocalmemory/infra/cache_manager.py +0 -267
- package/src/superlocalmemory/infra/cloud_backup.py +0 -788
- package/src/superlocalmemory/infra/daemon_identity.py +0 -300
- package/src/superlocalmemory/infra/data_root.py +0 -238
- package/src/superlocalmemory/infra/event_bus.py +0 -637
- package/src/superlocalmemory/infra/local_diagnostics.py +0 -327
- package/src/superlocalmemory/infra/pid_manager.py +0 -193
- package/src/superlocalmemory/infra/process_identity.py +0 -180
- package/src/superlocalmemory/infra/process_reaper.py +0 -624
- package/src/superlocalmemory/infra/rate_limiter.py +0 -228
- package/src/superlocalmemory/infra/self_heal.py +0 -401
- package/src/superlocalmemory/infra/version_integrity.py +0 -229
- package/src/superlocalmemory/ingestion/__init__.py +0 -13
- package/src/superlocalmemory/ingestion/adapter_manager.py +0 -255
- package/src/superlocalmemory/ingestion/base_adapter.py +0 -171
- package/src/superlocalmemory/ingestion/calendar_adapter.py +0 -349
- package/src/superlocalmemory/ingestion/credentials.py +0 -125
- package/src/superlocalmemory/ingestion/gmail_adapter.py +0 -378
- package/src/superlocalmemory/ingestion/parsers.py +0 -100
- package/src/superlocalmemory/ingestion/transcript_adapter.py +0 -161
- package/src/superlocalmemory/integrations/__init__.py +0 -1
- package/src/superlocalmemory/integrations/bounded_loops_mcp.py +0 -431
- package/src/superlocalmemory/integrations/bounded_loops_v051.py +0 -236
- package/src/superlocalmemory/learning/__init__.py +0 -0
- package/src/superlocalmemory/learning/adaptive.py +0 -172
- package/src/superlocalmemory/learning/arm_catalog.py +0 -97
- package/src/superlocalmemory/learning/assertion_miner.py +0 -403
- package/src/superlocalmemory/learning/bandit.py +0 -654
- package/src/superlocalmemory/learning/bandit_cache.py +0 -131
- package/src/superlocalmemory/learning/behavioral.py +0 -542
- package/src/superlocalmemory/learning/bootstrap.py +0 -298
- package/src/superlocalmemory/learning/consolidation_cycle.py +0 -398
- package/src/superlocalmemory/learning/consolidation_quantization_worker.py +0 -115
- package/src/superlocalmemory/learning/consolidation_worker.py +0 -261
- package/src/superlocalmemory/learning/cross_project.py +0 -408
- package/src/superlocalmemory/learning/database.py +0 -698
- package/src/superlocalmemory/learning/dedup_hnsw.py +0 -413
- package/src/superlocalmemory/learning/engagement.py +0 -487
- package/src/superlocalmemory/learning/engagement_features.py +0 -279
- package/src/superlocalmemory/learning/ensemble.py +0 -309
- package/src/superlocalmemory/learning/entity_compiler.py +0 -356
- package/src/superlocalmemory/learning/fact_outcome_joins.py +0 -207
- package/src/superlocalmemory/learning/features.py +0 -138
- package/src/superlocalmemory/learning/feedback.py +0 -724
- package/src/superlocalmemory/learning/forgetting_scheduler.py +0 -375
- package/src/superlocalmemory/learning/hnsw_dedup.py +0 -69
- package/src/superlocalmemory/learning/labeler.py +0 -85
- package/src/superlocalmemory/learning/legacy_migration.py +0 -316
- package/src/superlocalmemory/learning/lightgbm_subprocess.py +0 -236
- package/src/superlocalmemory/learning/memory_merge.py +0 -175
- package/src/superlocalmemory/learning/model_cache.py +0 -267
- package/src/superlocalmemory/learning/model_rollback.py +0 -281
- package/src/superlocalmemory/learning/outcome_queue.py +0 -306
- package/src/superlocalmemory/learning/outcomes.py +0 -286
- package/src/superlocalmemory/learning/pattern_miner.py +0 -465
- package/src/superlocalmemory/learning/pattern_miner_constants.py +0 -90
- package/src/superlocalmemory/learning/pcos.py +0 -291
- package/src/superlocalmemory/learning/project_context.py +0 -366
- package/src/superlocalmemory/learning/propensity.py +0 -131
- package/src/superlocalmemory/learning/ranker.py +0 -300
- package/src/superlocalmemory/learning/ranker_common.py +0 -163
- package/src/superlocalmemory/learning/ranker_retrain_legacy.py +0 -210
- package/src/superlocalmemory/learning/ranker_retrain_online.py +0 -423
- package/src/superlocalmemory/learning/reward.py +0 -888
- package/src/superlocalmemory/learning/reward_archive.py +0 -223
- package/src/superlocalmemory/learning/reward_boost.py +0 -211
- package/src/superlocalmemory/learning/reward_from_outcomes.py +0 -365
- package/src/superlocalmemory/learning/reward_model.py +0 -144
- package/src/superlocalmemory/learning/reward_proxy.py +0 -578
- package/src/superlocalmemory/learning/shadow_test.py +0 -524
- package/src/superlocalmemory/learning/signal_kinds.py +0 -79
- package/src/superlocalmemory/learning/signal_worker.py +0 -268
- package/src/superlocalmemory/learning/signals.py +0 -646
- package/src/superlocalmemory/learning/skill_performance_miner.py +0 -422
- package/src/superlocalmemory/learning/source_quality.py +0 -828
- package/src/superlocalmemory/learning/trigram_index.py +0 -548
- package/src/superlocalmemory/learning/workflows.py +0 -309
- package/src/superlocalmemory/llm/__init__.py +0 -0
- package/src/superlocalmemory/llm/backbone.py +0 -364
- package/src/superlocalmemory/loops/__init__.py +0 -58
- package/src/superlocalmemory/loops/budget.py +0 -58
- package/src/superlocalmemory/loops/engine.py +0 -174
- package/src/superlocalmemory/loops/ledger.py +0 -298
- package/src/superlocalmemory/loops/models.py +0 -152
- package/src/superlocalmemory/loops/rules.py +0 -52
- package/src/superlocalmemory/math/__init__.py +0 -0
- package/src/superlocalmemory/math/ebbinghaus.py +0 -352
- package/src/superlocalmemory/math/fisher.py +0 -356
- package/src/superlocalmemory/math/fisher_quantized.py +0 -255
- package/src/superlocalmemory/math/hopfield.py +0 -282
- package/src/superlocalmemory/math/langevin.py +0 -411
- package/src/superlocalmemory/math/polar_quant.py +0 -414
- package/src/superlocalmemory/math/qjl.py +0 -115
- package/src/superlocalmemory/math/sheaf.py +0 -261
- package/src/superlocalmemory/math/turbo_quant.py +0 -318
- package/src/superlocalmemory/mcp/__init__.py +0 -0
- package/src/superlocalmemory/mcp/_daemon_proxy.py +0 -203
- package/src/superlocalmemory/mcp/_pool_adapter.py +0 -181
- package/src/superlocalmemory/mcp/_stdin_guard.py +0 -60
- package/src/superlocalmemory/mcp/agent_context.py +0 -115
- package/src/superlocalmemory/mcp/cli_fallback.py +0 -602
- package/src/superlocalmemory/mcp/http_transport.py +0 -85
- package/src/superlocalmemory/mcp/profiles.py +0 -154
- package/src/superlocalmemory/mcp/resources.py +0 -281
- package/src/superlocalmemory/mcp/server.py +0 -480
- package/src/superlocalmemory/mcp/session_binding.py +0 -98
- package/src/superlocalmemory/mcp/shared.py +0 -112
- package/src/superlocalmemory/mcp/tools.py +0 -18
- package/src/superlocalmemory/mcp/tools_active.py +0 -958
- package/src/superlocalmemory/mcp/tools_brain.py +0 -298
- package/src/superlocalmemory/mcp/tools_code_graph.py +0 -1717
- package/src/superlocalmemory/mcp/tools_context.py +0 -239
- package/src/superlocalmemory/mcp/tools_core.py +0 -1112
- package/src/superlocalmemory/mcp/tools_evolution.py +0 -343
- package/src/superlocalmemory/mcp/tools_learning.py +0 -393
- package/src/superlocalmemory/mcp/tools_loops.py +0 -345
- package/src/superlocalmemory/mcp/tools_mesh.py +0 -429
- package/src/superlocalmemory/mcp/tools_ops.py +0 -115
- package/src/superlocalmemory/mcp/tools_optimize.py +0 -322
- package/src/superlocalmemory/mcp/tools_summaries.py +0 -147
- package/src/superlocalmemory/mcp/tools_v28.py +0 -292
- package/src/superlocalmemory/mcp/tools_v3.py +0 -398
- package/src/superlocalmemory/mcp/tools_v33.py +0 -507
- package/src/superlocalmemory/mesh/__init__.py +0 -12
- package/src/superlocalmemory/mesh/broker.py +0 -812
- package/src/superlocalmemory/mesh/broker_security.py +0 -470
- package/src/superlocalmemory/mesh/discovery.py +0 -365
- package/src/superlocalmemory/mesh/lock_protocol.py +0 -313
- package/src/superlocalmemory/mesh/node_identity.py +0 -97
- package/src/superlocalmemory/mesh/outbox_remote.py +0 -429
- package/src/superlocalmemory/mesh/remote_sync.py +0 -829
- package/src/superlocalmemory/mesh/state_sync.py +0 -286
- package/src/superlocalmemory/migrations/__init__.py +0 -5
- package/src/superlocalmemory/migrations/v3_4_25_to_v3_4_26.py +0 -144
- package/src/superlocalmemory/optimize/NOTICE +0 -6
- package/src/superlocalmemory/optimize/__init__.py +0 -0
- package/src/superlocalmemory/optimize/adapters/__init__.py +0 -68
- package/src/superlocalmemory/optimize/adapters/_agent_registry.py +0 -120
- package/src/superlocalmemory/optimize/adapters/anthropic_adapter.py +0 -112
- package/src/superlocalmemory/optimize/adapters/openai_adapter.py +0 -122
- package/src/superlocalmemory/optimize/adapters/wrap.py +0 -228
- package/src/superlocalmemory/optimize/cache/__init__.py +0 -31
- package/src/superlocalmemory/optimize/cache/boundary_store.py +0 -488
- package/src/superlocalmemory/optimize/cache/centroid_store.py +0 -199
- package/src/superlocalmemory/optimize/cache/context_key.py +0 -67
- package/src/superlocalmemory/optimize/cache/exact.py +0 -88
- package/src/superlocalmemory/optimize/cache/invalidation.py +0 -36
- package/src/superlocalmemory/optimize/cache/key_builder.py +0 -111
- package/src/superlocalmemory/optimize/cache/manager.py +0 -737
- package/src/superlocalmemory/optimize/cache/semantic.py +0 -637
- package/src/superlocalmemory/optimize/cache/stampede.py +0 -50
- package/src/superlocalmemory/optimize/compress/__init__.py +0 -17
- package/src/superlocalmemory/optimize/compress/align.py +0 -159
- package/src/superlocalmemory/optimize/compress/ccr.py +0 -116
- package/src/superlocalmemory/optimize/compress/prose_llmlingua.py +0 -71
- package/src/superlocalmemory/optimize/compress/router.py +0 -667
- package/src/superlocalmemory/optimize/config/__init__.py +0 -56
- package/src/superlocalmemory/optimize/config/defaults.py +0 -43
- package/src/superlocalmemory/optimize/config/schema.py +0 -327
- package/src/superlocalmemory/optimize/config/store.py +0 -270
- package/src/superlocalmemory/optimize/metrics/__init__.py +0 -8
- package/src/superlocalmemory/optimize/metrics/counters.py +0 -155
- package/src/superlocalmemory/optimize/metrics/estimator.py +0 -87
- package/src/superlocalmemory/optimize/metrics/exporters.py +0 -77
- package/src/superlocalmemory/optimize/metrics/persistence.py +0 -115
- package/src/superlocalmemory/optimize/proxy/__init__.py +0 -28
- package/src/superlocalmemory/optimize/proxy/_helpers.py +0 -730
- package/src/superlocalmemory/optimize/proxy/anthropic_surface.py +0 -375
- package/src/superlocalmemory/optimize/proxy/capture.py +0 -550
- package/src/superlocalmemory/optimize/proxy/gemini_surface.py +0 -528
- package/src/superlocalmemory/optimize/proxy/lifecycle.py +0 -126
- package/src/superlocalmemory/optimize/proxy/openai_surface.py +0 -465
- package/src/superlocalmemory/optimize/proxy/server.py +0 -199
- package/src/superlocalmemory/optimize/proxy/vertex_surface.py +0 -246
- package/src/superlocalmemory/optimize/storage/__init__.py +0 -0
- package/src/superlocalmemory/optimize/storage/db.py +0 -1185
- package/src/superlocalmemory/optimize/storage/schema.py +0 -205
- package/src/superlocalmemory/parameterization/__init__.py +0 -47
- package/src/superlocalmemory/parameterization/cross_project.py +0 -12
- package/src/superlocalmemory/parameterization/pattern_extractor.py +0 -584
- package/src/superlocalmemory/parameterization/pii_filter.py +0 -106
- package/src/superlocalmemory/parameterization/prompt_injector.py +0 -219
- package/src/superlocalmemory/parameterization/prompt_lifecycle.py +0 -281
- package/src/superlocalmemory/parameterization/soft_prompt_generator.py +0 -542
- package/src/superlocalmemory/parameterization/workflow_miner.py +0 -17
- package/src/superlocalmemory/reliability/__init__.py +0 -45
- package/src/superlocalmemory/reliability/join_liveness.py +0 -301
- package/src/superlocalmemory/reliability/prior_distance.py +0 -243
- package/src/superlocalmemory/retrieval/__init__.py +0 -0
- package/src/superlocalmemory/retrieval/agentic.py +0 -367
- package/src/superlocalmemory/retrieval/ann_index.py +0 -235
- package/src/superlocalmemory/retrieval/bm25_channel.py +0 -451
- package/src/superlocalmemory/retrieval/bridge_discovery.py +0 -253
- package/src/superlocalmemory/retrieval/channel_registry.py +0 -154
- package/src/superlocalmemory/retrieval/channel_status.py +0 -117
- package/src/superlocalmemory/retrieval/engine.py +0 -1615
- package/src/superlocalmemory/retrieval/entity_channel.py +0 -994
- package/src/superlocalmemory/retrieval/forgetting_filter.py +0 -160
- package/src/superlocalmemory/retrieval/fusion.py +0 -81
- package/src/superlocalmemory/retrieval/graph_adjacency.py +0 -219
- package/src/superlocalmemory/retrieval/hopfield_channel.py +0 -465
- package/src/superlocalmemory/retrieval/profile_channel.py +0 -105
- package/src/superlocalmemory/retrieval/quantization_aware_search.py +0 -147
- package/src/superlocalmemory/retrieval/remote_reranker.py +0 -758
- package/src/superlocalmemory/retrieval/reranker.py +0 -674
- package/src/superlocalmemory/retrieval/scope_policy.py +0 -126
- package/src/superlocalmemory/retrieval/semantic_channel.py +0 -638
- package/src/superlocalmemory/retrieval/spreading.py +0 -288
- package/src/superlocalmemory/retrieval/spreading_activation.py +0 -616
- package/src/superlocalmemory/retrieval/strategy.py +0 -248
- package/src/superlocalmemory/retrieval/temporal_channel.py +0 -433
- package/src/superlocalmemory/retrieval/temporal_frame.py +0 -102
- package/src/superlocalmemory/retrieval/temporal_utils.py +0 -122
- package/src/superlocalmemory/retrieval/temporal_validity_filter.py +0 -499
- package/src/superlocalmemory/retrieval/time_window.py +0 -181
- package/src/superlocalmemory/retrieval/vector_store.py +0 -863
- package/src/superlocalmemory/server/__init__.py +0 -1
- package/src/superlocalmemory/server/api.py +0 -310
- package/src/superlocalmemory/server/asset_versions.py +0 -171
- package/src/superlocalmemory/server/bandit_loops.py +0 -158
- package/src/superlocalmemory/server/config_file.py +0 -90
- package/src/superlocalmemory/server/consolidation_runner.py +0 -140
- package/src/superlocalmemory/server/egress_policy.py +0 -258
- package/src/superlocalmemory/server/loopback.py +0 -85
- package/src/superlocalmemory/server/middleware/__init__.py +0 -11
- package/src/superlocalmemory/server/middleware/security_headers.py +0 -144
- package/src/superlocalmemory/server/origin.py +0 -55
- package/src/superlocalmemory/server/profile_runtime.py +0 -515
- package/src/superlocalmemory/server/rbac_enforce.py +0 -194
- package/src/superlocalmemory/server/recall_health.py +0 -343
- package/src/superlocalmemory/server/recall_serializer.py +0 -320
- package/src/superlocalmemory/server/route_mutations.py +0 -104
- package/src/superlocalmemory/server/routes/__init__.py +0 -4
- package/src/superlocalmemory/server/routes/abstraction.py +0 -314
- package/src/superlocalmemory/server/routes/adapters.py +0 -63
- package/src/superlocalmemory/server/routes/agents.py +0 -303
- package/src/superlocalmemory/server/routes/backup.py +0 -869
- package/src/superlocalmemory/server/routes/behavioral.py +0 -659
- package/src/superlocalmemory/server/routes/brain.py +0 -1892
- package/src/superlocalmemory/server/routes/chat.py +0 -393
- package/src/superlocalmemory/server/routes/compliance.py +0 -533
- package/src/superlocalmemory/server/routes/config_api.py +0 -703
- package/src/superlocalmemory/server/routes/data_io.py +0 -329
- package/src/superlocalmemory/server/routes/entity.py +0 -237
- package/src/superlocalmemory/server/routes/events.py +0 -214
- package/src/superlocalmemory/server/routes/evolution.py +0 -510
- package/src/superlocalmemory/server/routes/helpers.py +0 -499
- package/src/superlocalmemory/server/routes/ingest.py +0 -137
- package/src/superlocalmemory/server/routes/insights.py +0 -366
- package/src/superlocalmemory/server/routes/learning.py +0 -834
- package/src/superlocalmemory/server/routes/learning_telemetry.py +0 -154
- package/src/superlocalmemory/server/routes/lifecycle.py +0 -184
- package/src/superlocalmemory/server/routes/memories.py +0 -1661
- package/src/superlocalmemory/server/routes/mesh.py +0 -517
- package/src/superlocalmemory/server/routes/mesh_lock.py +0 -54
- package/src/superlocalmemory/server/routes/mesh_state.py +0 -63
- package/src/superlocalmemory/server/routes/optimize.py +0 -197
- package/src/superlocalmemory/server/routes/prewarm.py +0 -173
- package/src/superlocalmemory/server/routes/profiles.py +0 -292
- package/src/superlocalmemory/server/routes/ratelimit.py +0 -132
- package/src/superlocalmemory/server/routes/rbac.py +0 -366
- package/src/superlocalmemory/server/routes/stats.py +0 -385
- package/src/superlocalmemory/server/routes/tiers.py +0 -222
- package/src/superlocalmemory/server/routes/timeline.py +0 -258
- package/src/superlocalmemory/server/routes/token.py +0 -90
- package/src/superlocalmemory/server/routes/v3_api.py +0 -3023
- package/src/superlocalmemory/server/routes/ws.py +0 -171
- package/src/superlocalmemory/server/security_middleware.py +0 -89
- package/src/superlocalmemory/server/ui.py +0 -354
- package/src/superlocalmemory/server/unified_daemon.py +0 -6326
- package/src/superlocalmemory/server/write_identity.py +0 -195
- package/src/superlocalmemory/storage/__init__.py +0 -0
- package/src/superlocalmemory/storage/_migration_internals.py +0 -638
- package/src/superlocalmemory/storage/_schema_version.py +0 -174
- package/src/superlocalmemory/storage/access_log.py +0 -170
- package/src/superlocalmemory/storage/admission_codec.py +0 -129
- package/src/superlocalmemory/storage/admission_journal.py +0 -843
- package/src/superlocalmemory/storage/agent_experience.py +0 -546
- package/src/superlocalmemory/storage/backup.py +0 -531
- package/src/superlocalmemory/storage/correction_cases.py +0 -670
- package/src/superlocalmemory/storage/database.py +0 -3180
- package/src/superlocalmemory/storage/deferred_writes.py +0 -209
- package/src/superlocalmemory/storage/embedding_codec.py +0 -200
- package/src/superlocalmemory/storage/embedding_migrator.py +0 -672
- package/src/superlocalmemory/storage/erasure_fence.py +0 -45
- package/src/superlocalmemory/storage/execution_learning.py +0 -285
- package/src/superlocalmemory/storage/external_evidence.py +0 -359
- package/src/superlocalmemory/storage/generation_fence.py +0 -63
- package/src/superlocalmemory/storage/lineage_retention.py +0 -236
- package/src/superlocalmemory/storage/logical_edges.py +0 -86
- package/src/superlocalmemory/storage/memory_write.py +0 -115
- package/src/superlocalmemory/storage/migration_runner.py +0 -895
- package/src/superlocalmemory/storage/migration_v33.py +0 -140
- package/src/superlocalmemory/storage/migrations/M001_add_signal_features_columns.py +0 -67
- package/src/superlocalmemory/storage/migrations/M002_model_state_history.py +0 -107
- package/src/superlocalmemory/storage/migrations/M003_migration_log.py +0 -38
- package/src/superlocalmemory/storage/migrations/M004_cross_platform_sync_log.py +0 -46
- package/src/superlocalmemory/storage/migrations/M005_bandit_tables.py +0 -75
- package/src/superlocalmemory/storage/migrations/M006_action_outcomes_reward.py +0 -75
- package/src/superlocalmemory/storage/migrations/M007_pending_outcomes.py +0 -63
- package/src/superlocalmemory/storage/migrations/M009_model_lineage.py +0 -94
- package/src/superlocalmemory/storage/migrations/M010_evolution_config.py +0 -80
- package/src/superlocalmemory/storage/migrations/M011_archive_and_merge.py +0 -87
- package/src/superlocalmemory/storage/migrations/M012_shadow_observations.py +0 -72
- package/src/superlocalmemory/storage/migrations/M013_bi_temporal_columns.py +0 -55
- package/src/superlocalmemory/storage/migrations/M014_v345_scale_ready.py +0 -45
- package/src/superlocalmemory/storage/migrations/M015_add_pinned_column.py +0 -58
- package/src/superlocalmemory/storage/migrations/M016_add_scope_support.py +0 -120
- package/src/superlocalmemory/storage/migrations/M017_ccq_scope_column.py +0 -79
- package/src/superlocalmemory/storage/migrations/M018_ingestion_operations.py +0 -120
- package/src/superlocalmemory/storage/migrations/M019_derivation_lineage.py +0 -54
- package/src/superlocalmemory/storage/migrations/M020_model_state_integrity.py +0 -52
- package/src/superlocalmemory/storage/migrations/M021_ingestion_log_profile.py +0 -108
- package/src/superlocalmemory/storage/migrations/M022_entity_aliases_profile.py +0 -86
- package/src/superlocalmemory/storage/migrations/M023_mesh_profile_isolation.py +0 -194
- package/src/superlocalmemory/storage/migrations/M024_rbac_users_roles.py +0 -87
- package/src/superlocalmemory/storage/migrations/M025_perf_indexes.py +0 -90
- package/src/superlocalmemory/storage/migrations/M026_rbac_memberships_fk.py +0 -136
- package/src/superlocalmemory/storage/migrations/M027_transferable_patterns_profile.py +0 -163
- package/src/superlocalmemory/storage/migrations/M028_fact_entity_associations.py +0 -305
- package/src/superlocalmemory/storage/migrations/M029_behavioral_history_indexes.py +0 -137
- package/src/superlocalmemory/storage/migrations/M030_entity_explorer_indexes.py +0 -93
- package/src/superlocalmemory/storage/migrations/M031_dead_letter_operations.py +0 -80
- package/src/superlocalmemory/storage/migrations/M032_write_coordinator_admission.py +0 -188
- package/src/superlocalmemory/storage/migrations/M033_projection_transactions.py +0 -148
- package/src/superlocalmemory/storage/migrations/M034_obligation_integrity.py +0 -58
- package/src/superlocalmemory/storage/migrations/M035_erasure_receipts.py +0 -113
- package/src/superlocalmemory/storage/migrations/M036_vector_row_map.py +0 -107
- package/src/superlocalmemory/storage/migrations/M037_manifest_hmac_version.py +0 -162
- package/src/superlocalmemory/storage/migrations/M038_learning_feedback_channel.py +0 -77
- package/src/superlocalmemory/storage/migrations/M039_scene_fact_members.py +0 -137
- package/src/superlocalmemory/storage/migrations/M040_agent_experience_receipts.py +0 -254
- package/src/superlocalmemory/storage/migrations/M041_external_evidence_receipts.py +0 -189
- package/src/superlocalmemory/storage/migrations/M042_correction_case_ledger.py +0 -245
- package/src/superlocalmemory/storage/migrations/M043_quarantine_display_summaries.py +0 -512
- package/src/superlocalmemory/storage/migrations/M044_play_carries_its_own_evidence.py +0 -127
- package/src/superlocalmemory/storage/migrations/M045_fact_outcome_score.py +0 -158
- package/src/superlocalmemory/storage/migrations/M046_prospective_memory_has_its_own_name.py +0 -620
- package/src/superlocalmemory/storage/migrations/M047_fisher_vectors_are_stored_like_every_other_vector.py +0 -306
- package/src/superlocalmemory/storage/migrations/M048_upcoming_holds_only_what_is_upcoming.py +0 -229
- package/src/superlocalmemory/storage/migrations/M049_a_schema_version_marker_is_one_row.py +0 -201
- package/src/superlocalmemory/storage/migrations/M050_execution_learning_v2.py +0 -70
- package/src/superlocalmemory/storage/migrations/__init__.py +0 -103
- package/src/superlocalmemory/storage/migrations.py +0 -333
- package/src/superlocalmemory/storage/models.py +0 -500
- package/src/superlocalmemory/storage/projection_outbox.py +0 -346
- package/src/superlocalmemory/storage/quantized_store.py +0 -280
- package/src/superlocalmemory/storage/read_connection.py +0 -115
- package/src/superlocalmemory/storage/retention_policy.py +0 -860
- package/src/superlocalmemory/storage/schema.py +0 -1108
- package/src/superlocalmemory/storage/schema_code_graph.py +0 -282
- package/src/superlocalmemory/storage/schema_v32.py +0 -382
- package/src/superlocalmemory/storage/schema_v3410.py +0 -159
- package/src/superlocalmemory/storage/schema_v3411.py +0 -149
- package/src/superlocalmemory/storage/schema_v343.py +0 -315
- package/src/superlocalmemory/storage/schema_v345.py +0 -109
- package/src/superlocalmemory/storage/schema_v347.py +0 -140
- package/src/superlocalmemory/storage/sqlite_vectors.py +0 -169
- package/src/superlocalmemory/storage/v2_migrator.py +0 -466
- package/src/superlocalmemory/storage/write_coordinator.py +0 -949
- package/src/superlocalmemory/storage/write_lock.py +0 -88
- package/src/superlocalmemory/summaries/__init__.py +0 -37
- package/src/superlocalmemory/summaries/base.py +0 -267
- package/src/superlocalmemory/summaries/daily_reflection.py +0 -340
- package/src/superlocalmemory/summaries/non_answer.py +0 -223
- package/src/superlocalmemory/summaries/project_work_log.py +0 -440
- package/src/superlocalmemory/summaries/session_summary.py +0 -311
- package/src/superlocalmemory/trust/__init__.py +0 -0
- package/src/superlocalmemory/trust/gate.py +0 -171
- package/src/superlocalmemory/trust/provenance.py +0 -124
- package/src/superlocalmemory/trust/scorer.py +0 -413
- package/src/superlocalmemory/trust/signals.py +0 -153
- package/src/superlocalmemory/ui/assets/slm-icon-white.svg +0 -64
- package/src/superlocalmemory/ui/assets/slm-icon.svg +0 -36
- package/src/superlocalmemory/ui/css/brain.css +0 -409
- package/src/superlocalmemory/ui/css/design-system.css +0 -696
- package/src/superlocalmemory/ui/css/legacy-dashboard.css +0 -663
- package/src/superlocalmemory/ui/css/neural-glass.css +0 -1599
- package/src/superlocalmemory/ui/css/od-bridge.css +0 -158
- package/src/superlocalmemory/ui/favicon.svg +0 -36
- package/src/superlocalmemory/ui/index.html +0 -1638
- package/src/superlocalmemory/ui/js/agents.js +0 -192
- package/src/superlocalmemory/ui/js/auto-settings.js +0 -624
- package/src/superlocalmemory/ui/js/brain.js +0 -1400
- package/src/superlocalmemory/ui/js/clusters.js +0 -326
- package/src/superlocalmemory/ui/js/compliance.js +0 -307
- package/src/superlocalmemory/ui/js/core.js +0 -566
- package/src/superlocalmemory/ui/js/dashboard.js +0 -503
- package/src/superlocalmemory/ui/js/event-delegation.js +0 -113
- package/src/superlocalmemory/ui/js/events.js +0 -178
- package/src/superlocalmemory/ui/js/fact-detail.js +0 -142
- package/src/superlocalmemory/ui/js/feedback.js +0 -339
- package/src/superlocalmemory/ui/js/graph-event-bus.js +0 -83
- package/src/superlocalmemory/ui/js/graph-filters.js +0 -220
- package/src/superlocalmemory/ui/js/graph-ui.js +0 -214
- package/src/superlocalmemory/ui/js/ide-status.js +0 -115
- package/src/superlocalmemory/ui/js/init.js +0 -54
- package/src/superlocalmemory/ui/js/knowledge-graph.js +0 -945
- package/src/superlocalmemory/ui/js/lifecycle.js +0 -387
- package/src/superlocalmemory/ui/js/math-health.js +0 -114
- package/src/superlocalmemory/ui/js/memories.js +0 -394
- package/src/superlocalmemory/ui/js/memory-chat.js +0 -371
- package/src/superlocalmemory/ui/js/memory-timeline.js +0 -265
- package/src/superlocalmemory/ui/js/modal.js +0 -733
- package/src/superlocalmemory/ui/js/ng-entities.js +0 -298
- package/src/superlocalmemory/ui/js/ng-health.js +0 -208
- package/src/superlocalmemory/ui/js/ng-ingestion.js +0 -203
- package/src/superlocalmemory/ui/js/ng-mesh.js +0 -374
- package/src/superlocalmemory/ui/js/ng-shell.js +0 -524
- package/src/superlocalmemory/ui/js/ng-skills.js +0 -663
- package/src/superlocalmemory/ui/js/od-agents.js +0 -588
- package/src/superlocalmemory/ui/js/od-auth-gate.js +0 -257
- package/src/superlocalmemory/ui/js/od-backup.js +0 -878
- package/src/superlocalmemory/ui/js/od-boundedloops.js +0 -324
- package/src/superlocalmemory/ui/js/od-brain.js +0 -1095
- package/src/superlocalmemory/ui/js/od-compliance-ext.js +0 -301
- package/src/superlocalmemory/ui/js/od-components.js +0 -147
- package/src/superlocalmemory/ui/js/od-entities.js +0 -622
- package/src/superlocalmemory/ui/js/od-graph.js +0 -776
- package/src/superlocalmemory/ui/js/od-health.js +0 -579
- package/src/superlocalmemory/ui/js/od-mcp.js +0 -508
- package/src/superlocalmemory/ui/js/od-memories.js +0 -1499
- package/src/superlocalmemory/ui/js/od-mesh.js +0 -645
- package/src/superlocalmemory/ui/js/od-operations.js +0 -1268
- package/src/superlocalmemory/ui/js/od-ops-health.js +0 -417
- package/src/superlocalmemory/ui/js/od-optimize.js +0 -828
- package/src/superlocalmemory/ui/js/od-settings.js +0 -1275
- package/src/superlocalmemory/ui/js/od-shell.js +0 -819
- package/src/superlocalmemory/ui/js/od-skills.js +0 -600
- package/src/superlocalmemory/ui/js/od-team.js +0 -265
- package/src/superlocalmemory/ui/js/optimize.js +0 -191
- package/src/superlocalmemory/ui/js/profiles.js +0 -362
- package/src/superlocalmemory/ui/js/quick-actions.js +0 -334
- package/src/superlocalmemory/ui/js/recall-lab.js +0 -373
- package/src/superlocalmemory/ui/js/search.js +0 -86
- package/src/superlocalmemory/ui/js/settings.js +0 -556
- package/src/superlocalmemory/ui/js/timeline.js +0 -62
- package/src/superlocalmemory/ui/js/trust-dashboard.js +0 -225
- package/src/superlocalmemory/ui/vendor/bootstrap-icons/bootstrap-icons.css +0 -2018
- package/src/superlocalmemory/ui/vendor/bootstrap-icons/fonts/bootstrap-icons.woff +0 -0
- package/src/superlocalmemory/ui/vendor/bootstrap-icons/fonts/bootstrap-icons.woff2 +0 -0
- package/src/superlocalmemory/ui/vendor/bootstrap.bundle.min.js +0 -7
- package/src/superlocalmemory/ui/vendor/bootstrap.min.css +0 -6
- package/src/superlocalmemory/ui/vendor/d3.v7.min.js +0 -2
- package/src/superlocalmemory/ui/vendor/graphology-library.min.js +0 -2
- package/src/superlocalmemory/ui/vendor/graphology.umd.min.js +0 -2
- package/src/superlocalmemory/ui/vendor/inter-ui/inter-variable.min.css +0 -8
- package/src/superlocalmemory/ui/vendor/inter-ui/variable/InterVariable-Italic.woff2 +0 -0
- package/src/superlocalmemory/ui/vendor/inter-ui/variable/InterVariable.woff2 +0 -0
- package/src/superlocalmemory/ui/vendor/sigma.min.js +0 -1
- package/src/superlocalmemory/vector/__init__.py +0 -9
- package/src/superlocalmemory/vector/lancedb_backend.py +0 -366
|
@@ -1,829 +0,0 @@
|
|
|
1
|
-
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
|
-
# Licensed under AGPL-3.0-or-later - see LICENSE file
|
|
3
|
-
# Part of SuperLocalMemory V3 | https://qualixar.com | https://varunpratap.com
|
|
4
|
-
|
|
5
|
-
"""SLM Mesh — Remote Sync Client.
|
|
6
|
-
|
|
7
|
-
HTTP-based synchronization with a remote SLM instance.
|
|
8
|
-
Populates broker._remote_peers from the remote /mesh/peers endpoint.
|
|
9
|
-
Proxies mesh_send to remote when the target peer lives on the remote machine.
|
|
10
|
-
Optional mDNS discovery via zeroconf.
|
|
11
|
-
|
|
12
|
-
New in 3b-1: durable remote outbox — failed sends are enqueued and retried
|
|
13
|
-
with exponential back-off until delivered or TTL-expired.
|
|
14
|
-
New in 3b-3: TLS-pinned transport — opt-in SHA-256 cert pinning + custom CA.
|
|
15
|
-
|
|
16
|
-
Environment variables:
|
|
17
|
-
SLM_MESH_PEER_URL: Full URL of remote SLM (e.g. http://192.168.1.100:8765)
|
|
18
|
-
SLM_MESH_SHARED_SECRET: Shared auth secret for remote SLM
|
|
19
|
-
SLM_MESH_DISCOVERY: 'on'|'off' (default 'on') — enable mDNS discovery
|
|
20
|
-
SLM_MESH_TLS: 'on'|'off' (default 'off') — use https:// for discovered peers
|
|
21
|
-
SLM_MESH_TLS_CA: Path to custom CA bundle (PEM) for TLS verification
|
|
22
|
-
SLM_MESH_TLS_PIN: Hex SHA-256 of peer leaf cert DER for cert pinning
|
|
23
|
-
"""
|
|
24
|
-
|
|
25
|
-
from __future__ import annotations
|
|
26
|
-
|
|
27
|
-
import hashlib
|
|
28
|
-
import json
|
|
29
|
-
import logging
|
|
30
|
-
import os
|
|
31
|
-
import socket
|
|
32
|
-
import ssl
|
|
33
|
-
import threading
|
|
34
|
-
import time
|
|
35
|
-
import ipaddress
|
|
36
|
-
from typing import Any
|
|
37
|
-
|
|
38
|
-
import httpx
|
|
39
|
-
|
|
40
|
-
logger = logging.getLogger("superlocalmemory.mesh.remote_sync")
|
|
41
|
-
|
|
42
|
-
_PRODUCTION_TRUTHY = frozenset({"1", "true", "yes", "on", "production", "prod"})
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def _is_production_mode() -> bool:
|
|
46
|
-
"""True when SLM_MESH_PRODUCTION signals a hardened deployment."""
|
|
47
|
-
return os.environ.get("SLM_MESH_PRODUCTION", "").strip().lower() in _PRODUCTION_TRUTHY
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def _is_plaintext_url(url: str) -> bool:
|
|
51
|
-
"""True for http:// and ws:// URLs (not TLS-protected)."""
|
|
52
|
-
lower = url.strip().lower()
|
|
53
|
-
return lower.startswith("http://") or lower.startswith("ws://")
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def _service_ip_addresses(info: Any) -> list[str]:
|
|
57
|
-
"""Return validated textual IPs from current and older Zeroconf APIs."""
|
|
58
|
-
candidates: list[Any] = []
|
|
59
|
-
parsed = getattr(info, "parsed_addresses", None)
|
|
60
|
-
if callable(parsed):
|
|
61
|
-
try:
|
|
62
|
-
candidates.extend(parsed())
|
|
63
|
-
except (OSError, TypeError, ValueError):
|
|
64
|
-
pass
|
|
65
|
-
candidates.extend(getattr(info, "addresses", None) or [])
|
|
66
|
-
|
|
67
|
-
addresses: list[str] = []
|
|
68
|
-
for candidate in candidates:
|
|
69
|
-
try:
|
|
70
|
-
address = ipaddress.ip_address(candidate)
|
|
71
|
-
except (TypeError, ValueError):
|
|
72
|
-
continue
|
|
73
|
-
# An mDNS peer must name a routable endpoint, never a wildcard or
|
|
74
|
-
# multicast destination. Authentication is still enforced by mesh.
|
|
75
|
-
if address.is_unspecified or address.is_multicast:
|
|
76
|
-
continue
|
|
77
|
-
rendered = str(address)
|
|
78
|
-
if rendered not in addresses:
|
|
79
|
-
addresses.append(rendered)
|
|
80
|
-
return addresses
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
def _peer_url(host: str, port: int) -> str:
|
|
84
|
-
"""Format an IP literal safely for an HTTP authority.
|
|
85
|
-
|
|
86
|
-
Always produces http:// — preserved exactly for backward compatibility.
|
|
87
|
-
Use _peer_url_with_scheme() when an https:// override is needed.
|
|
88
|
-
"""
|
|
89
|
-
address = ipaddress.ip_address(host)
|
|
90
|
-
rendered = str(address)
|
|
91
|
-
if address.version == 6:
|
|
92
|
-
# RFC 6874 requires a percent sign in an IPv6 zone identifier to be
|
|
93
|
-
# escaped when the literal appears inside a URI authority.
|
|
94
|
-
rendered = rendered.replace("%", "%25")
|
|
95
|
-
rendered = f"[{rendered}]"
|
|
96
|
-
return f"http://{rendered}:{int(port)}"
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
def _peer_url_with_scheme(host: str, port: int, tls: bool = False) -> str:
|
|
100
|
-
"""Format an IP literal safely for an HTTP or HTTPS authority.
|
|
101
|
-
|
|
102
|
-
Args:
|
|
103
|
-
host: IP address string (v4 or v6).
|
|
104
|
-
port: Port number.
|
|
105
|
-
tls: When True, emit https:// instead of http://.
|
|
106
|
-
|
|
107
|
-
Returns:
|
|
108
|
-
Scheme-correct URL string.
|
|
109
|
-
"""
|
|
110
|
-
address = ipaddress.ip_address(host)
|
|
111
|
-
rendered = str(address)
|
|
112
|
-
if address.version == 6:
|
|
113
|
-
rendered = rendered.replace("%", "%25")
|
|
114
|
-
rendered = f"[{rendered}]"
|
|
115
|
-
scheme = "https" if tls else "http"
|
|
116
|
-
return f"{scheme}://{rendered}:{int(port)}"
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
def _get_cert_sha256(
|
|
120
|
-
host: str,
|
|
121
|
-
port: int,
|
|
122
|
-
ca_file: str | None = None,
|
|
123
|
-
timeout: int = 5,
|
|
124
|
-
) -> str:
|
|
125
|
-
"""Return hex SHA-256 of the server's leaf certificate DER bytes.
|
|
126
|
-
|
|
127
|
-
Opens a raw TLS connection, retrieves the peer cert in DER form, and
|
|
128
|
-
returns its SHA-256 digest as a lowercase hex string (no colons).
|
|
129
|
-
|
|
130
|
-
Raises:
|
|
131
|
-
ssl.SSLError: TLS negotiation failed or cert not trusted.
|
|
132
|
-
socket.error: Connection refused or timed out.
|
|
133
|
-
OSError: Any underlying network error.
|
|
134
|
-
"""
|
|
135
|
-
ctx = ssl.create_default_context(cafile=ca_file)
|
|
136
|
-
with socket.create_connection((host, port), timeout=timeout) as raw_sock:
|
|
137
|
-
with ctx.wrap_socket(raw_sock, server_hostname=host) as tls_sock:
|
|
138
|
-
cert_der = tls_sock.getpeercert(binary_form=True)
|
|
139
|
-
if not cert_der:
|
|
140
|
-
raise ssl.SSLError("peer returned no certificate")
|
|
141
|
-
return hashlib.sha256(cert_der).hexdigest().lower()
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
# Optional zeroconf for mDNS discovery
|
|
145
|
-
try:
|
|
146
|
-
from zeroconf import ServiceBrowser, ServiceInfo, Zeroconf
|
|
147
|
-
ZEROCONF_AVAILABLE = True
|
|
148
|
-
except ImportError:
|
|
149
|
-
ZEROCONF_AVAILABLE = False
|
|
150
|
-
Zeroconf = None
|
|
151
|
-
ServiceBrowser = None
|
|
152
|
-
ServiceInfo = None
|
|
153
|
-
|
|
154
|
-
# Optional durable outbox (3b-1) — fail-open so missing module never breaks start()
|
|
155
|
-
try:
|
|
156
|
-
from .outbox_remote import RemoteOutbox
|
|
157
|
-
_OUTBOX_AVAILABLE = True
|
|
158
|
-
except ImportError:
|
|
159
|
-
RemoteOutbox = None # type: ignore[assignment,misc]
|
|
160
|
-
_OUTBOX_AVAILABLE = False
|
|
161
|
-
|
|
162
|
-
#: Wall-clock budget for a single outbox drain pass. Bounds how long a
|
|
163
|
-
#: slow/black-holing peer can monopolize the shared 30s sync thread (audit
|
|
164
|
-
#: P1); remaining due rows are deferred to the next cycle.
|
|
165
|
-
_DRAIN_BUDGET_SECONDS: float = 8.0
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
class RemoteSyncClient:
|
|
169
|
-
"""HTTP-based sync client for multi-machine mesh coordination.
|
|
170
|
-
|
|
171
|
-
Syncs remote peers from a peer SLM instance periodically.
|
|
172
|
-
Proxies mesh_send to remote when target peer lives on remote machine.
|
|
173
|
-
Optionally discovers remote SLM via mDNS.
|
|
174
|
-
|
|
175
|
-
3b-1: Failed sends are stored in a durable SQLite outbox and retried
|
|
176
|
-
with exponential back-off so messages survive peer downtime.
|
|
177
|
-
|
|
178
|
-
3b-3: Optional TLS cert pinning (SLM_MESH_TLS_PIN) and custom CA
|
|
179
|
-
(SLM_MESH_TLS_CA). Default behavior (no env) is byte-for-byte identical
|
|
180
|
-
to previous releases.
|
|
181
|
-
"""
|
|
182
|
-
|
|
183
|
-
def __init__(self, broker: Any) -> None:
|
|
184
|
-
"""Initialize sync client.
|
|
185
|
-
|
|
186
|
-
Args:
|
|
187
|
-
broker: Reference to MeshBroker instance.
|
|
188
|
-
"""
|
|
189
|
-
self._broker = broker
|
|
190
|
-
self._peer_url: str | None = os.environ.get("SLM_MESH_PEER_URL") or None
|
|
191
|
-
self._shared_secret: str | None = os.environ.get("SLM_MESH_SHARED_SECRET") or None
|
|
192
|
-
self._discovery_enabled: bool = (
|
|
193
|
-
os.environ.get("SLM_MESH_DISCOVERY", "on") != "off"
|
|
194
|
-
)
|
|
195
|
-
# M05: the shared secret is a bearer token — whoever receives it can
|
|
196
|
-
# replay it. mDNS discovery is unauthenticated (anyone on the LAN can
|
|
197
|
-
# advertise _slm-mesh._tcp.local.), so we must NOT push the secret to a
|
|
198
|
-
# discovered peer unless the operator explicitly trusts LAN discovery.
|
|
199
|
-
# An explicitly-configured peer (SLM_MESH_PEER_URL) is trusted; a peer
|
|
200
|
-
# set programmatically stays trusted; only the discovery path can
|
|
201
|
-
# downgrade trust.
|
|
202
|
-
self._peer_url_from_config: bool = self._peer_url is not None
|
|
203
|
-
self._trust_discovered: bool = (
|
|
204
|
-
os.environ.get("SLM_MESH_TRUST_DISCOVERED", "off").strip().lower()
|
|
205
|
-
in ("1", "on", "true", "yes")
|
|
206
|
-
)
|
|
207
|
-
self._peer_url_trusted: bool = True
|
|
208
|
-
self._sync_thread: threading.Thread | None = None
|
|
209
|
-
self._discovery_thread: threading.Thread | None = None
|
|
210
|
-
self._stop_event = threading.Event()
|
|
211
|
-
self._zeroconf: Zeroconf | None = None
|
|
212
|
-
self._last_peers: dict[str, dict] = {}
|
|
213
|
-
# 3b-1: lazily initialised durable outbox — None until first access
|
|
214
|
-
self._outbox: RemoteOutbox | None = None # type: ignore[type-arg]
|
|
215
|
-
|
|
216
|
-
def start(self) -> None:
|
|
217
|
-
"""Start background sync and discovery threads.
|
|
218
|
-
|
|
219
|
-
In production mode (SLM_MESH_PRODUCTION=1), plaintext peer URLs
|
|
220
|
-
(http:// or ws://) are rejected so credentials cannot be sent in
|
|
221
|
-
the clear and traffic cannot be intercepted.
|
|
222
|
-
"""
|
|
223
|
-
if self._peer_url and _is_production_mode() and _is_plaintext_url(self._peer_url):
|
|
224
|
-
raise ValueError(
|
|
225
|
-
f"Production mesh requires TLS transport (https:// or wss://); "
|
|
226
|
-
f"got plaintext peer URL: {self._peer_url!r}. "
|
|
227
|
-
"Set SLM_MESH_PEER_URL to an https:// endpoint or "
|
|
228
|
-
"unset SLM_MESH_PRODUCTION to allow plaintext in dev/local mode."
|
|
229
|
-
)
|
|
230
|
-
|
|
231
|
-
if not self._peer_url and not self._discovery_enabled:
|
|
232
|
-
logger.debug(
|
|
233
|
-
"RemoteSyncClient: no peer URL and discovery disabled, skipping"
|
|
234
|
-
)
|
|
235
|
-
return
|
|
236
|
-
|
|
237
|
-
# Start sync thread
|
|
238
|
-
self._sync_thread = threading.Thread(
|
|
239
|
-
target=self._sync_loop, daemon=True, name="mesh-remote-sync"
|
|
240
|
-
)
|
|
241
|
-
self._sync_thread.start()
|
|
242
|
-
|
|
243
|
-
# Start discovery thread if enabled and zeroconf available
|
|
244
|
-
if self._discovery_enabled and ZEROCONF_AVAILABLE:
|
|
245
|
-
self._discovery_thread = threading.Thread(
|
|
246
|
-
target=self._discovery_loop, daemon=True, name="mesh-mdns-discovery"
|
|
247
|
-
)
|
|
248
|
-
self._discovery_thread.start()
|
|
249
|
-
logger.info("RemoteSyncClient: mDNS discovery enabled")
|
|
250
|
-
elif self._discovery_enabled and not ZEROCONF_AVAILABLE:
|
|
251
|
-
logger.warning(
|
|
252
|
-
"RemoteSyncClient: mDNS discovery requested but zeroconf not available"
|
|
253
|
-
)
|
|
254
|
-
|
|
255
|
-
def stop(self) -> None:
|
|
256
|
-
"""Stop background threads cleanly."""
|
|
257
|
-
self._stop_event.set()
|
|
258
|
-
if self._zeroconf:
|
|
259
|
-
try:
|
|
260
|
-
self._zeroconf.close()
|
|
261
|
-
except Exception as e:
|
|
262
|
-
logger.debug("RemoteSyncClient: error closing zeroconf: %s", e)
|
|
263
|
-
# Wait for threads to finish (up to 2s)
|
|
264
|
-
if self._sync_thread:
|
|
265
|
-
self._sync_thread.join(timeout=2)
|
|
266
|
-
if self._discovery_thread:
|
|
267
|
-
self._discovery_thread.join(timeout=2)
|
|
268
|
-
|
|
269
|
-
# ------------------------------------------------------------------
|
|
270
|
-
# TLS helpers (3b-3)
|
|
271
|
-
# ------------------------------------------------------------------
|
|
272
|
-
|
|
273
|
-
def _http_client(self, timeout: int) -> httpx.Client:
|
|
274
|
-
"""Build an httpx.Client with appropriate TLS configuration.
|
|
275
|
-
|
|
276
|
-
Honors SLM_MESH_TLS_CA (custom CA bundle path). When unset, uses
|
|
277
|
-
system CAs (verify=True). The cert-pin check is done separately in
|
|
278
|
-
_check_cert_pin() before the first byte is sent.
|
|
279
|
-
|
|
280
|
-
Default (no env) produces httpx.Client(timeout=timeout) — identical
|
|
281
|
-
to the previous hard-coded behavior.
|
|
282
|
-
"""
|
|
283
|
-
ca_path = os.environ.get("SLM_MESH_TLS_CA") or None
|
|
284
|
-
if ca_path:
|
|
285
|
-
return httpx.Client(verify=ca_path, timeout=timeout)
|
|
286
|
-
return httpx.Client(timeout=timeout)
|
|
287
|
-
|
|
288
|
-
def _check_cert_pin(self, peer_url: str) -> tuple[bool, str]:
|
|
289
|
-
"""Pre-flight SHA-256 certificate pin check for an https:// URL.
|
|
290
|
-
|
|
291
|
-
Opens a brief raw TLS connection to retrieve the leaf certificate,
|
|
292
|
-
computes its SHA-256 hash, and compares it to the configured pin.
|
|
293
|
-
This happens BEFORE the actual HTTP request so no payload is sent
|
|
294
|
-
to a peer with a mismatched certificate.
|
|
295
|
-
|
|
296
|
-
Normalization: both actual and expected hex strings are lowercased
|
|
297
|
-
and colon-stripped before comparison to avoid case-sensitivity bugs.
|
|
298
|
-
|
|
299
|
-
Args:
|
|
300
|
-
peer_url: URL being targeted.
|
|
301
|
-
|
|
302
|
-
Returns:
|
|
303
|
-
(True, "") if pin matches or no pin is configured.
|
|
304
|
-
(False, reason) if pin is configured and does not match.
|
|
305
|
-
"""
|
|
306
|
-
pin_env = os.environ.get("SLM_MESH_TLS_PIN") or None
|
|
307
|
-
if not pin_env:
|
|
308
|
-
return True, ""
|
|
309
|
-
|
|
310
|
-
parsed = httpx.URL(peer_url)
|
|
311
|
-
if parsed.scheme != "https":
|
|
312
|
-
# A pin is configured but the peer URL is not https. Pinning is
|
|
313
|
-
# impossible over plaintext, so FAIL CLOSED (audit P1) rather than
|
|
314
|
-
# silently sending in the clear under a false sense of pinning.
|
|
315
|
-
return False, (
|
|
316
|
-
f"SLM_MESH_TLS_PIN is set but peer URL scheme is "
|
|
317
|
-
f"{parsed.scheme!r}, not https — refusing to send unpinned "
|
|
318
|
-
"plaintext (set an https:// peer URL or unset the pin)"
|
|
319
|
-
)
|
|
320
|
-
|
|
321
|
-
host = parsed.host
|
|
322
|
-
port = parsed.port or 443
|
|
323
|
-
# Normalise: lowercase and strip colons (handles both plain hex and
|
|
324
|
-
# colon-separated fingerprint formats e.g. "AB:CD:...").
|
|
325
|
-
expected_pin = pin_env.strip().lower().replace(":", "")
|
|
326
|
-
ca_path = os.environ.get("SLM_MESH_TLS_CA") or None
|
|
327
|
-
|
|
328
|
-
try:
|
|
329
|
-
actual_pin = _get_cert_sha256(host, port, ca_file=ca_path)
|
|
330
|
-
except (ssl.SSLError, socket.error, OSError) as exc:
|
|
331
|
-
return False, f"cert pin check connection failed: {exc}"
|
|
332
|
-
|
|
333
|
-
if actual_pin != expected_pin:
|
|
334
|
-
return False, (
|
|
335
|
-
f"certificate pin mismatch for {host}:{port} "
|
|
336
|
-
f"(expected {expected_pin!r}, got {actual_pin!r})"
|
|
337
|
-
)
|
|
338
|
-
return True, ""
|
|
339
|
-
|
|
340
|
-
# ------------------------------------------------------------------
|
|
341
|
-
# Outbox helpers (3b-1)
|
|
342
|
-
# ------------------------------------------------------------------
|
|
343
|
-
|
|
344
|
-
def _get_outbox(self) -> RemoteOutbox | None: # type: ignore[return]
|
|
345
|
-
"""Lazily initialise the RemoteOutbox using the broker's DB path.
|
|
346
|
-
|
|
347
|
-
Returns None (and logs once) if the outbox cannot be initialised
|
|
348
|
-
so the online send path is always unaffected.
|
|
349
|
-
"""
|
|
350
|
-
if self._outbox is not None:
|
|
351
|
-
return self._outbox
|
|
352
|
-
if not _OUTBOX_AVAILABLE:
|
|
353
|
-
return None
|
|
354
|
-
db_path = getattr(self._broker, "_db_path", None)
|
|
355
|
-
if not db_path:
|
|
356
|
-
logger.debug("RemoteSyncClient: no db_path on broker — outbox disabled")
|
|
357
|
-
return None
|
|
358
|
-
try:
|
|
359
|
-
self._outbox = RemoteOutbox(db_path) # type: ignore[call-arg]
|
|
360
|
-
except Exception as exc:
|
|
361
|
-
logger.error("RemoteSyncClient: failed to init RemoteOutbox: %s", exc)
|
|
362
|
-
return self._outbox
|
|
363
|
-
|
|
364
|
-
def _enqueue_on_failure(
|
|
365
|
-
self,
|
|
366
|
-
peer_url: str,
|
|
367
|
-
to_peer: str,
|
|
368
|
-
payload: dict[str, Any],
|
|
369
|
-
headers: dict[str, str],
|
|
370
|
-
now: float,
|
|
371
|
-
) -> None:
|
|
372
|
-
"""Enqueue a failed send to the durable outbox.
|
|
373
|
-
|
|
374
|
-
Headers are stored for audit purposes only. The drain loop re-signs
|
|
375
|
-
fresh headers (new nonce + timestamp) before each retry attempt to
|
|
376
|
-
avoid stale-timestamp rejections at the receiving peer.
|
|
377
|
-
"""
|
|
378
|
-
outbox = self._get_outbox()
|
|
379
|
-
if outbox is not None:
|
|
380
|
-
outbox.enqueue(peer_url, to_peer, payload, headers, now=now)
|
|
381
|
-
|
|
382
|
-
def _build_signed_headers(
|
|
383
|
-
self,
|
|
384
|
-
payload: dict[str, Any],
|
|
385
|
-
to_peer: str,
|
|
386
|
-
base_headers: dict[str, str],
|
|
387
|
-
) -> dict[str, str]:
|
|
388
|
-
"""Add fresh HMAC signing headers to base_headers if a secret is configured.
|
|
389
|
-
|
|
390
|
-
Generates a new nonce + current timestamp so each signing is unique
|
|
391
|
-
and replay-safe. Returns base_headers unchanged when signing is
|
|
392
|
-
not applicable.
|
|
393
|
-
"""
|
|
394
|
-
if not (self._shared_secret and self._peer_url_trusted):
|
|
395
|
-
return dict(base_headers)
|
|
396
|
-
|
|
397
|
-
import secrets as _sec
|
|
398
|
-
from .broker_security import sign_mesh_message
|
|
399
|
-
|
|
400
|
-
from_peer = payload.get("from_peer", "")
|
|
401
|
-
content = payload.get("content", "")
|
|
402
|
-
nonce = _sec.token_hex(16)
|
|
403
|
-
ts = str(int(time.time()))
|
|
404
|
-
sig = sign_mesh_message(
|
|
405
|
-
self._shared_secret, from_peer, to_peer, content, nonce, ts,
|
|
406
|
-
)
|
|
407
|
-
return {
|
|
408
|
-
**base_headers,
|
|
409
|
-
"X-Mesh-Sig": sig,
|
|
410
|
-
"X-Mesh-Nonce": nonce,
|
|
411
|
-
"X-Mesh-Ts": ts,
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
def _drain_outbox(self) -> None:
|
|
415
|
-
"""Re-attempt delivery for due outbox items (called from _sync_loop).
|
|
416
|
-
|
|
417
|
-
Processes up to _BATCH_LIMIT rows per call (bounded by outbox.due()).
|
|
418
|
-
Each row gets fresh signing headers. On success: deleted. On any
|
|
419
|
-
failure: mark_retry() with exponential back-off. Always prunes
|
|
420
|
-
expired rows at the end of the drain pass.
|
|
421
|
-
|
|
422
|
-
This method only runs when self._peer_url is set (guarded in
|
|
423
|
-
_sync_loop) — when no peer is configured the outbox is inert.
|
|
424
|
-
"""
|
|
425
|
-
outbox = self._get_outbox()
|
|
426
|
-
if outbox is None:
|
|
427
|
-
return
|
|
428
|
-
|
|
429
|
-
now = time.time()
|
|
430
|
-
due_rows = outbox.due(now)
|
|
431
|
-
if not due_rows:
|
|
432
|
-
outbox.prune_expired(now)
|
|
433
|
-
return
|
|
434
|
-
|
|
435
|
-
for row in due_rows:
|
|
436
|
-
# Audit P1: bound wall-clock so a slow/black-holing peer can't
|
|
437
|
-
# monopolize the shared 30s sync thread. Remaining due rows stay
|
|
438
|
-
# queued and are retried next cycle.
|
|
439
|
-
if time.time() - now > _DRAIN_BUDGET_SECONDS:
|
|
440
|
-
logger.debug(
|
|
441
|
-
"RemoteOutbox: drain budget (%.0fs) exhausted; deferring "
|
|
442
|
-
"remaining rows to next cycle",
|
|
443
|
-
_DRAIN_BUDGET_SECONDS,
|
|
444
|
-
)
|
|
445
|
-
break
|
|
446
|
-
row_id: int = row["id"]
|
|
447
|
-
peer_url: str = row["peer_url"]
|
|
448
|
-
to_peer: str = row["to_peer"]
|
|
449
|
-
|
|
450
|
-
try:
|
|
451
|
-
payload: dict[str, Any] = json.loads(row["payload"])
|
|
452
|
-
except (json.JSONDecodeError, ValueError) as exc:
|
|
453
|
-
logger.debug(
|
|
454
|
-
"RemoteOutbox: corrupt payload in row %d — deleting: %s",
|
|
455
|
-
row_id, exc,
|
|
456
|
-
)
|
|
457
|
-
outbox.delete(row_id)
|
|
458
|
-
continue
|
|
459
|
-
|
|
460
|
-
# Pre-flight pin check (3b-3)
|
|
461
|
-
pin_ok, pin_err = self._check_cert_pin(peer_url)
|
|
462
|
-
if not pin_ok:
|
|
463
|
-
logger.debug(
|
|
464
|
-
"RemoteOutbox: pin check failed for row %d (%s): %s",
|
|
465
|
-
row_id, peer_url, pin_err,
|
|
466
|
-
)
|
|
467
|
-
outbox.mark_retry(row_id, now)
|
|
468
|
-
continue
|
|
469
|
-
|
|
470
|
-
# Rebuild fresh headers: auth bearer + fresh HMAC signature
|
|
471
|
-
base_headers = self._auth_headers()
|
|
472
|
-
full_headers = self._build_signed_headers(payload, to_peer, base_headers)
|
|
473
|
-
|
|
474
|
-
try:
|
|
475
|
-
with self._http_client(timeout=10) as client:
|
|
476
|
-
resp = client.post(
|
|
477
|
-
f"{peer_url}/mesh/send",
|
|
478
|
-
json=payload,
|
|
479
|
-
headers=full_headers,
|
|
480
|
-
timeout=10,
|
|
481
|
-
)
|
|
482
|
-
resp.raise_for_status()
|
|
483
|
-
|
|
484
|
-
outbox.delete(row_id)
|
|
485
|
-
logger.debug(
|
|
486
|
-
"RemoteOutbox: delivered row %d → %s (to_peer=%s)",
|
|
487
|
-
row_id, peer_url, to_peer,
|
|
488
|
-
)
|
|
489
|
-
except httpx.RequestError as exc:
|
|
490
|
-
logger.debug(
|
|
491
|
-
"RemoteOutbox: HTTP error for row %d: %s", row_id, exc
|
|
492
|
-
)
|
|
493
|
-
outbox.mark_retry(row_id, now)
|
|
494
|
-
except httpx.HTTPStatusError as exc:
|
|
495
|
-
logger.debug(
|
|
496
|
-
"RemoteOutbox: non-2xx for row %d: %s", row_id, exc
|
|
497
|
-
)
|
|
498
|
-
outbox.mark_retry(row_id, now)
|
|
499
|
-
except Exception as exc:
|
|
500
|
-
logger.debug(
|
|
501
|
-
"RemoteOutbox: unexpected error for row %d: %s", row_id, exc
|
|
502
|
-
)
|
|
503
|
-
outbox.mark_retry(row_id, now)
|
|
504
|
-
|
|
505
|
-
outbox.prune_expired(now)
|
|
506
|
-
|
|
507
|
-
# ------------------------------------------------------------------
|
|
508
|
-
# Core sync loop
|
|
509
|
-
# ------------------------------------------------------------------
|
|
510
|
-
|
|
511
|
-
def _sync_protocol_from_remote(self) -> None:
|
|
512
|
-
"""3c: pull the peer's state + lock deltas and converge locally.
|
|
513
|
-
|
|
514
|
-
- State: deterministic LWW merge (StateSyncer.merge_remote).
|
|
515
|
-
- Locks: fencing-token resolution (LockCoordinator.resolve).
|
|
516
|
-
|
|
517
|
-
Fail-soft: any error is logged and never interrupts the sync loop.
|
|
518
|
-
Honors the SAME cert pin as peer-sync/send (fail-closed) so a MITM peer
|
|
519
|
-
cannot feed us forged deltas or harvest the bearer token. Scoped to the
|
|
520
|
-
"default" profile (single-profile mesh); per-profile sync is a
|
|
521
|
-
documented follow-up.
|
|
522
|
-
"""
|
|
523
|
-
if not self._peer_url:
|
|
524
|
-
return
|
|
525
|
-
pin_ok, pin_err = self._check_cert_pin(self._peer_url)
|
|
526
|
-
if not pin_ok:
|
|
527
|
-
logger.debug("RemoteSyncClient: protocol-sync pin check failed: %s", pin_err)
|
|
528
|
-
return
|
|
529
|
-
try:
|
|
530
|
-
from .lock_protocol import LockCoordinator
|
|
531
|
-
from .state_sync import StateSyncer
|
|
532
|
-
except Exception as exc: # pragma: no cover — modules always present
|
|
533
|
-
logger.debug("RemoteSyncClient: protocol modules unavailable: %s", exc)
|
|
534
|
-
return
|
|
535
|
-
|
|
536
|
-
profile = "default"
|
|
537
|
-
headers = self._auth_headers()
|
|
538
|
-
|
|
539
|
-
# State delta → LWW merge.
|
|
540
|
-
try:
|
|
541
|
-
with self._http_client(timeout=5) as client:
|
|
542
|
-
resp = client.get(
|
|
543
|
-
f"{self._peer_url}/mesh/state/delta", headers=headers, timeout=5
|
|
544
|
-
)
|
|
545
|
-
resp.raise_for_status()
|
|
546
|
-
entries = resp.json().get("entries", [])
|
|
547
|
-
if entries:
|
|
548
|
-
StateSyncer(self._broker).merge_remote(profile, entries)
|
|
549
|
-
except httpx.RequestError as exc:
|
|
550
|
-
logger.debug("RemoteSyncClient: state-delta sync error: %s", exc)
|
|
551
|
-
except Exception as exc:
|
|
552
|
-
logger.debug("RemoteSyncClient: state-delta merge error: %s", exc)
|
|
553
|
-
|
|
554
|
-
# Lock delta → fencing-token resolution.
|
|
555
|
-
try:
|
|
556
|
-
with self._http_client(timeout=5) as client:
|
|
557
|
-
resp = client.get(
|
|
558
|
-
f"{self._peer_url}/mesh/lock/delta", headers=headers, timeout=5
|
|
559
|
-
)
|
|
560
|
-
resp.raise_for_status()
|
|
561
|
-
locks = resp.json().get("locks", [])
|
|
562
|
-
if locks:
|
|
563
|
-
LockCoordinator(self._broker).resolve(profile, locks)
|
|
564
|
-
except httpx.RequestError as exc:
|
|
565
|
-
logger.debug("RemoteSyncClient: lock-delta sync error: %s", exc)
|
|
566
|
-
except Exception as exc:
|
|
567
|
-
logger.debug("RemoteSyncClient: lock-delta resolve error: %s", exc)
|
|
568
|
-
|
|
569
|
-
def _sync_loop(self) -> None:
|
|
570
|
-
"""Background thread: sync remote peers every 30s, then drain outbox."""
|
|
571
|
-
while not self._stop_event.is_set():
|
|
572
|
-
if self._peer_url:
|
|
573
|
-
try:
|
|
574
|
-
self._sync_peers_from_remote()
|
|
575
|
-
except Exception as exc:
|
|
576
|
-
logger.debug("RemoteSyncClient: sync error: %s", exc)
|
|
577
|
-
|
|
578
|
-
# 3b-1: drain outbox after every peer sync
|
|
579
|
-
try:
|
|
580
|
-
self._drain_outbox()
|
|
581
|
-
except Exception as exc:
|
|
582
|
-
logger.debug("RemoteSyncClient: outbox drain error: %s", exc)
|
|
583
|
-
|
|
584
|
-
# 3c: converge remote state (LWW) + locks (fencing) each cycle
|
|
585
|
-
try:
|
|
586
|
-
self._sync_protocol_from_remote()
|
|
587
|
-
except Exception as exc:
|
|
588
|
-
logger.debug("RemoteSyncClient: protocol sync error: %s", exc)
|
|
589
|
-
|
|
590
|
-
# Wait 30s before next sync
|
|
591
|
-
if self._stop_event.wait(30):
|
|
592
|
-
break
|
|
593
|
-
|
|
594
|
-
def _auth_headers(self) -> dict[str, str]:
|
|
595
|
-
"""Bearer header for the current peer — but ONLY if that peer is
|
|
596
|
-
trusted. Prevents leaking the shared secret to a spoofed mDNS peer
|
|
597
|
-
(M05)."""
|
|
598
|
-
if self._shared_secret and self._peer_url_trusted:
|
|
599
|
-
return {"Authorization": f"Bearer {self._shared_secret}"}
|
|
600
|
-
return {}
|
|
601
|
-
|
|
602
|
-
def _sync_peers_from_remote(self) -> None:
|
|
603
|
-
"""Fetch peers from remote /mesh/peers and update broker."""
|
|
604
|
-
if not self._peer_url:
|
|
605
|
-
return
|
|
606
|
-
|
|
607
|
-
# Audit P1: the peers-sync GET carries the bearer token — it MUST honor
|
|
608
|
-
# the same cert pin as send/drain, else a MITM (CA-valid wrong leaf)
|
|
609
|
-
# harvests the token here and poisons the remote-peer directory while
|
|
610
|
-
# /mesh/send is pin-blocked. Fail closed for this cycle on pin failure.
|
|
611
|
-
pin_ok, pin_err = self._check_cert_pin(self._peer_url)
|
|
612
|
-
if not pin_ok:
|
|
613
|
-
logger.debug(
|
|
614
|
-
"RemoteSyncClient: peer-sync pin check failed, skipping: %s",
|
|
615
|
-
pin_err,
|
|
616
|
-
)
|
|
617
|
-
return
|
|
618
|
-
|
|
619
|
-
try:
|
|
620
|
-
with self._http_client(timeout=5) as client:
|
|
621
|
-
headers = self._auth_headers()
|
|
622
|
-
|
|
623
|
-
resp = client.get(
|
|
624
|
-
f"{self._peer_url}/mesh/peers", headers=headers, timeout=5
|
|
625
|
-
)
|
|
626
|
-
resp.raise_for_status()
|
|
627
|
-
|
|
628
|
-
data = resp.json()
|
|
629
|
-
remote_peers = data.get("peers", [])
|
|
630
|
-
|
|
631
|
-
# Convert list to dict by peer_id
|
|
632
|
-
current = {p.get("peer_id"): p for p in remote_peers}
|
|
633
|
-
|
|
634
|
-
# Add/update peers
|
|
635
|
-
for peer_id, peer_info in current.items():
|
|
636
|
-
self._broker.add_remote_peer(peer_id, peer_info)
|
|
637
|
-
|
|
638
|
-
# Remove stale peers (ones that disappeared from remote)
|
|
639
|
-
for peer_id in list(self._last_peers.keys()):
|
|
640
|
-
if peer_id not in current:
|
|
641
|
-
self._broker.remove_remote_peer(peer_id)
|
|
642
|
-
|
|
643
|
-
self._last_peers = current
|
|
644
|
-
logger.debug(
|
|
645
|
-
"RemoteSyncClient: synced %d remote peers from %s",
|
|
646
|
-
len(current),
|
|
647
|
-
self._peer_url,
|
|
648
|
-
)
|
|
649
|
-
except httpx.RequestError as e:
|
|
650
|
-
logger.debug("RemoteSyncClient: HTTP error during sync: %s", e)
|
|
651
|
-
except Exception as e:
|
|
652
|
-
logger.debug("RemoteSyncClient: unexpected error during sync: %s", e)
|
|
653
|
-
|
|
654
|
-
def send_to_remote(self, to_peer: str, message_data: dict) -> dict:
|
|
655
|
-
"""Proxy mesh_send to remote /mesh/send endpoint.
|
|
656
|
-
|
|
657
|
-
On success: returns the remote response dict (unchanged from pre-3b).
|
|
658
|
-
On any failure (RequestError, non-2xx, unexpected): enqueues the
|
|
659
|
-
message to the durable outbox for retry, then returns the same
|
|
660
|
-
{"ok": False, ...} dict as before. The return contract is unchanged.
|
|
661
|
-
|
|
662
|
-
Args:
|
|
663
|
-
to_peer: Target peer ID on remote machine.
|
|
664
|
-
message_data: Dict with from_peer, content, type, etc.
|
|
665
|
-
|
|
666
|
-
Returns:
|
|
667
|
-
Dict with {"ok": True, ...} or {"ok": False, "error": "..."}.
|
|
668
|
-
"""
|
|
669
|
-
if not self._peer_url:
|
|
670
|
-
return {"ok": False, "error": "no remote peer URL configured"}
|
|
671
|
-
|
|
672
|
-
from_peer = message_data.get("from_peer", "")
|
|
673
|
-
content = message_data.get("content", "")
|
|
674
|
-
payload = {
|
|
675
|
-
"from_peer": from_peer,
|
|
676
|
-
"to_peer": to_peer,
|
|
677
|
-
"content": content,
|
|
678
|
-
"type": message_data.get("type", "text"),
|
|
679
|
-
}
|
|
680
|
-
# Everything below (pin pre-flight, signing, POST) runs INSIDE the try
|
|
681
|
-
# so any error returns the stable {"ok": False, ...} contract instead
|
|
682
|
-
# of propagating into the broker / route handler (audit P1 — restores
|
|
683
|
-
# the pre-3b fail-soft behavior).
|
|
684
|
-
try:
|
|
685
|
-
# 3b-3: pre-flight cert pin check before sending any data. A pin
|
|
686
|
-
# mismatch (or pin-set-without-https) is a PERMANENT config/attack
|
|
687
|
-
# condition — do NOT enqueue: retrying cannot fix it and would just
|
|
688
|
-
# fill the outbox with undeliverable rows (audit P2).
|
|
689
|
-
pin_ok, pin_err = self._check_cert_pin(self._peer_url)
|
|
690
|
-
if not pin_ok:
|
|
691
|
-
logger.warning(
|
|
692
|
-
"RemoteSyncClient: cert pin check failed for %s "
|
|
693
|
-
"(not enqueued): %s",
|
|
694
|
-
self._peer_url, pin_err,
|
|
695
|
-
)
|
|
696
|
-
return {"ok": False, "error": f"certificate pin failure: {pin_err}"}
|
|
697
|
-
|
|
698
|
-
signed_headers = self._build_signed_headers(
|
|
699
|
-
payload, to_peer, self._auth_headers()
|
|
700
|
-
)
|
|
701
|
-
|
|
702
|
-
with self._http_client(timeout=10) as client:
|
|
703
|
-
resp = client.post(
|
|
704
|
-
f"{self._peer_url}/mesh/send",
|
|
705
|
-
json=payload,
|
|
706
|
-
headers=signed_headers,
|
|
707
|
-
timeout=10,
|
|
708
|
-
)
|
|
709
|
-
resp.raise_for_status()
|
|
710
|
-
return resp.json()
|
|
711
|
-
|
|
712
|
-
except httpx.RequestError as e:
|
|
713
|
-
logger.debug(
|
|
714
|
-
"RemoteSyncClient: HTTP error sending to remote peer %s: %s",
|
|
715
|
-
to_peer, e,
|
|
716
|
-
)
|
|
717
|
-
# Audit P0: never persist auth headers (bearer secret) to disk —
|
|
718
|
-
# the drain re-signs from the in-memory secret. headers=None.
|
|
719
|
-
self._enqueue_on_failure(
|
|
720
|
-
self._peer_url, to_peer, payload, None, now=time.time()
|
|
721
|
-
)
|
|
722
|
-
return {"ok": False, "error": f"remote send failed: {e}"}
|
|
723
|
-
|
|
724
|
-
except httpx.HTTPStatusError as e:
|
|
725
|
-
logger.debug(
|
|
726
|
-
"RemoteSyncClient: non-2xx sending to remote peer %s: %s",
|
|
727
|
-
to_peer, e,
|
|
728
|
-
)
|
|
729
|
-
self._enqueue_on_failure(
|
|
730
|
-
self._peer_url, to_peer, payload, None, now=time.time()
|
|
731
|
-
)
|
|
732
|
-
return {"ok": False, "error": f"remote send non-2xx: {e}"}
|
|
733
|
-
|
|
734
|
-
except Exception as e:
|
|
735
|
-
logger.debug(
|
|
736
|
-
"RemoteSyncClient: unexpected error sending to remote peer %s: %s",
|
|
737
|
-
to_peer, e,
|
|
738
|
-
)
|
|
739
|
-
self._enqueue_on_failure(
|
|
740
|
-
self._peer_url, to_peer, payload, None, now=time.time()
|
|
741
|
-
)
|
|
742
|
-
return {"ok": False, "error": f"remote send error: {e}"}
|
|
743
|
-
|
|
744
|
-
def _discovery_loop(self) -> None:
|
|
745
|
-
"""Background thread: discover remote SLM via mDNS."""
|
|
746
|
-
if not ZEROCONF_AVAILABLE:
|
|
747
|
-
return
|
|
748
|
-
|
|
749
|
-
try:
|
|
750
|
-
self._zeroconf = Zeroconf()
|
|
751
|
-
ServiceBrowser(self._zeroconf, "_slm-mesh._tcp.local.", self)
|
|
752
|
-
logger.info("RemoteSyncClient: mDNS browser started")
|
|
753
|
-
|
|
754
|
-
# Keep thread alive
|
|
755
|
-
while not self._stop_event.is_set():
|
|
756
|
-
time.sleep(1)
|
|
757
|
-
except Exception as e:
|
|
758
|
-
logger.debug("RemoteSyncClient: mDNS discovery error: %s", e)
|
|
759
|
-
finally:
|
|
760
|
-
if self._zeroconf:
|
|
761
|
-
try:
|
|
762
|
-
self._zeroconf.close()
|
|
763
|
-
except Exception:
|
|
764
|
-
pass
|
|
765
|
-
|
|
766
|
-
def add_service(self, zeroconf: Any, service_type: str, name: str) -> None:
|
|
767
|
-
"""Zeroconf callback: service discovered."""
|
|
768
|
-
try:
|
|
769
|
-
if not ZEROCONF_AVAILABLE:
|
|
770
|
-
return
|
|
771
|
-
info = zeroconf.get_service_info(service_type, name)
|
|
772
|
-
if info:
|
|
773
|
-
for addr in _service_ip_addresses(info):
|
|
774
|
-
port = info.port or 8765
|
|
775
|
-
peer_url = _peer_url(addr, port)
|
|
776
|
-
self._update_peer_url(addr, port)
|
|
777
|
-
logger.info(
|
|
778
|
-
"RemoteSyncClient: discovered SLM at %s", peer_url
|
|
779
|
-
)
|
|
780
|
-
return
|
|
781
|
-
except Exception as e:
|
|
782
|
-
logger.debug("RemoteSyncClient: mDNS add_service error: %s", e)
|
|
783
|
-
|
|
784
|
-
def remove_service(self, zeroconf: Any, service_type: str, name: str) -> None:
|
|
785
|
-
"""Zeroconf callback: service disappeared."""
|
|
786
|
-
logger.debug("RemoteSyncClient: service removed: %s", name)
|
|
787
|
-
|
|
788
|
-
def update_service(self, zeroconf: Any, service_type: str, name: str) -> None:
|
|
789
|
-
"""Zeroconf callback: service updated."""
|
|
790
|
-
self.add_service(zeroconf, service_type, name)
|
|
791
|
-
|
|
792
|
-
def _update_peer_url(self, host: str, port: int) -> None:
|
|
793
|
-
"""Update peer URL from mDNS discovery.
|
|
794
|
-
|
|
795
|
-
Never overrides an explicitly-configured SLM_MESH_PEER_URL — explicit
|
|
796
|
-
config is the source of truth and must not be hijacked by a spoofed
|
|
797
|
-
mDNS announcement. A discovered peer is marked UNTRUSTED (the shared
|
|
798
|
-
secret is withheld) unless SLM_MESH_TRUST_DISCOVERED is enabled (M05).
|
|
799
|
-
|
|
800
|
-
3b-3: When SLM_MESH_TLS=on, discovered peers use https:// instead
|
|
801
|
-
of the default http://. Default OFF preserves today's behavior.
|
|
802
|
-
"""
|
|
803
|
-
if self._peer_url_from_config:
|
|
804
|
-
logger.debug(
|
|
805
|
-
"RemoteSyncClient: ignoring mDNS-discovered peer %s:%s — "
|
|
806
|
-
"SLM_MESH_PEER_URL is explicitly configured",
|
|
807
|
-
host, port,
|
|
808
|
-
)
|
|
809
|
-
return
|
|
810
|
-
tls_enabled = (
|
|
811
|
-
os.environ.get("SLM_MESH_TLS", "off").strip().lower()
|
|
812
|
-
in ("1", "on", "true", "yes")
|
|
813
|
-
)
|
|
814
|
-
new_url = _peer_url_with_scheme(host, port, tls=tls_enabled)
|
|
815
|
-
if self._peer_url != new_url:
|
|
816
|
-
self._peer_url = new_url
|
|
817
|
-
self._peer_url_trusted = self._trust_discovered
|
|
818
|
-
logger.info(
|
|
819
|
-
"RemoteSyncClient: updated peer URL to %s (mDNS-discovered, "
|
|
820
|
-
"trusted=%s)", new_url, self._peer_url_trusted,
|
|
821
|
-
)
|
|
822
|
-
if self._shared_secret and not self._trust_discovered:
|
|
823
|
-
logger.warning(
|
|
824
|
-
"RemoteSyncClient: a shared secret is set but "
|
|
825
|
-
"SLM_MESH_TRUST_DISCOVERED is off — the secret will NOT be "
|
|
826
|
-
"sent to mDNS-discovered peer %s. Set "
|
|
827
|
-
"SLM_MESH_TRUST_DISCOVERED=on to trust LAN-discovered peers.",
|
|
828
|
-
new_url,
|
|
829
|
-
)
|