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,812 +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 Broker — core orchestration for P2P agent communication.
|
|
6
|
-
|
|
7
|
-
Manages peer lifecycle, scheduled cleanup, and event logging.
|
|
8
|
-
All operations use the shared memory.db via SQLite tables with mesh_ prefix.
|
|
9
|
-
|
|
10
|
-
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
from __future__ import annotations
|
|
14
|
-
|
|
15
|
-
import logging
|
|
16
|
-
import sqlite3
|
|
17
|
-
import threading
|
|
18
|
-
import time
|
|
19
|
-
import uuid
|
|
20
|
-
from datetime import datetime, timedelta, timezone
|
|
21
|
-
from pathlib import Path
|
|
22
|
-
from typing import Any, Callable, TypeVar
|
|
23
|
-
|
|
24
|
-
logger = logging.getLogger("superlocalmemory.mesh")
|
|
25
|
-
import os as _os
|
|
26
|
-
|
|
27
|
-
from .broker_security import ( # noqa: E501
|
|
28
|
-
apply_security_schema, check_cross_profile_sender, ensure_db_healthy, get_or_create_peer_key, reject_secret_state, scrub_message_content, seed_fencing_counter, _set_nonce_db_path, validate_lock_fence_query, # noqa: E501
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
# Remote sync support (optional, try/except to avoid import issues)
|
|
32
|
-
try:
|
|
33
|
-
from .remote_sync import RemoteSyncClient
|
|
34
|
-
except ImportError:
|
|
35
|
-
RemoteSyncClient = None # type: ignore
|
|
36
|
-
|
|
37
|
-
LOCAL_HOSTS = frozenset({"127.0.0.1", "localhost", "::1"})
|
|
38
|
-
|
|
39
|
-
MAX_MESSAGE_SIZE = 4096 # 4KB cap — mesh messages are notifications, not data dumps
|
|
40
|
-
MESSAGE_TTL_HOURS = 48 # Offline messages expire after 48h
|
|
41
|
-
LOCK_TTL_HOURS = 8 # M-02: file locks auto-expire so a crashed session can't deadlock a path
|
|
42
|
-
_NEVER_EXPIRES = "9999-12-31T23:59:59Z" # legacy sentinel default; treated as stale/free
|
|
43
|
-
MAX_QUEUED_PER_TARGET = 50 # Max unread messages per broadcast/project target
|
|
44
|
-
|
|
45
|
-
_T = TypeVar("_T")
|
|
46
|
-
# Retry budget for a transient writer collision. 250ms was too thin under
|
|
47
|
-
# multi-agent load; 2000ms matches the RBAC store. 4 attempts keeps the total
|
|
48
|
-
# worst-case budget bounded (~2s x backoff) rather than unbounded stalls.
|
|
49
|
-
_WRITE_RETRY_ATTEMPTS = 4
|
|
50
|
-
_WRITE_RETRY_BASE_SECONDS = 0.025
|
|
51
|
-
_WRITE_BUSY_TIMEOUT_MS = 2000
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
class MeshBroker:
|
|
55
|
-
"""Lightweight mesh broker — peer lifecycle, messaging, state, locks, events."""
|
|
56
|
-
|
|
57
|
-
def __init__(self, db_path: str | Path):
|
|
58
|
-
self._db_path = str(db_path)
|
|
59
|
-
self._started_at = time.monotonic()
|
|
60
|
-
self._cleanup_thread: threading.Thread | None = None
|
|
61
|
-
self._stop_event = threading.Event()
|
|
62
|
-
self._host = _os.environ.get("SLM_MESH_HOST", "127.0.0.1")
|
|
63
|
-
self._shared_secret = _os.environ.get("SLM_MESH_SHARED_SECRET", "") or None
|
|
64
|
-
self._is_remote = self._host not in LOCAL_HOSTS
|
|
65
|
-
self._ws_port = int(_os.environ.get("SLM_MESH_WS_PORT", "7900"))
|
|
66
|
-
self._discovery_enabled = self._is_remote and _os.environ.get("SLM_MESH_DISCOVERY", "on") != "off"
|
|
67
|
-
self._remote_peers: dict[str, dict] = {}
|
|
68
|
-
self._remote_peers_lock = threading.RLock()
|
|
69
|
-
self._peer_url: str | None = _os.environ.get("SLM_MESH_PEER_URL", "") or None
|
|
70
|
-
self._sync_client: Any = None
|
|
71
|
-
self._degraded: bool = False
|
|
72
|
-
self._fencing_lock = threading.Lock()
|
|
73
|
-
self._fencing_counter: int = 0 # seeded below after schema is applied
|
|
74
|
-
if self._is_remote and not self._shared_secret:
|
|
75
|
-
raise RuntimeError(
|
|
76
|
-
"SLM_MESH_SHARED_SECRET is required when SLM_MESH_HOST is not localhost"
|
|
77
|
-
)
|
|
78
|
-
self._ensure_db_healthy()
|
|
79
|
-
self._apply_schema_updates()
|
|
80
|
-
_set_nonce_db_path(self._db_path) # SEC-3: wire SQLite nonce store for durability
|
|
81
|
-
self._fencing_counter = seed_fencing_counter(self._db_path) # 3a-3: restart-safe fence
|
|
82
|
-
|
|
83
|
-
# -- Remote / Multi-Machine support (v3.4.47) --
|
|
84
|
-
|
|
85
|
-
def get_remote_peers(self, profile_id: str = "default") -> list[dict]:
|
|
86
|
-
"""Return peers from discovered remote brokers for this tenant.
|
|
87
|
-
|
|
88
|
-
Remote peers carry their home ``profile_id`` in the info dict (populated
|
|
89
|
-
by the /mesh/peers sync). A peer with no profile_id is a legacy remote
|
|
90
|
-
and treated as 'default'. Cross-tenant peers are never returned.
|
|
91
|
-
"""
|
|
92
|
-
with self._remote_peers_lock:
|
|
93
|
-
return [
|
|
94
|
-
info for info in self._remote_peers.values()
|
|
95
|
-
if info.get("profile_id", "default") == profile_id
|
|
96
|
-
]
|
|
97
|
-
|
|
98
|
-
def add_remote_peer(self, peer_id: str, info: dict) -> None:
|
|
99
|
-
"""Register a peer from a remote broker."""
|
|
100
|
-
with self._remote_peers_lock:
|
|
101
|
-
self._remote_peers[peer_id] = info
|
|
102
|
-
|
|
103
|
-
def remove_remote_peer(self, peer_id: str) -> None:
|
|
104
|
-
"""Remove a remote peer."""
|
|
105
|
-
with self._remote_peers_lock:
|
|
106
|
-
self._remote_peers.pop(peer_id, None)
|
|
107
|
-
|
|
108
|
-
def list_all_peers(self, profile_id: str = "default") -> list[dict]:
|
|
109
|
-
"""Return local + remote peers for this tenant, merged."""
|
|
110
|
-
local = self.list_peers(profile_id)
|
|
111
|
-
remote = self.get_remote_peers(profile_id)
|
|
112
|
-
return local + remote
|
|
113
|
-
|
|
114
|
-
def start_cleanup(self) -> None:
|
|
115
|
-
"""Start background cleanup thread for stale peers/messages."""
|
|
116
|
-
self._cleanup_thread = threading.Thread(
|
|
117
|
-
target=self._cleanup_loop, daemon=True, name="mesh-cleanup",
|
|
118
|
-
)
|
|
119
|
-
self._cleanup_thread.start()
|
|
120
|
-
|
|
121
|
-
# Start remote sync client if peer URL configured or remote mode
|
|
122
|
-
if RemoteSyncClient and (
|
|
123
|
-
self._peer_url or (self._is_remote and self._host not in LOCAL_HOSTS)
|
|
124
|
-
):
|
|
125
|
-
self._sync_client = RemoteSyncClient(self)
|
|
126
|
-
self._sync_client.start()
|
|
127
|
-
|
|
128
|
-
def stop(self) -> None:
|
|
129
|
-
self._stop_event.set()
|
|
130
|
-
if self._sync_client:
|
|
131
|
-
self._sync_client.stop()
|
|
132
|
-
|
|
133
|
-
# -- Startup safety helpers --
|
|
134
|
-
|
|
135
|
-
def _ensure_db_healthy(self) -> None:
|
|
136
|
-
"""Quarantine a corrupt DB; set _degraded if recovery was needed."""
|
|
137
|
-
self._degraded = ensure_db_healthy(self._db_path)
|
|
138
|
-
|
|
139
|
-
def _apply_schema_updates(self) -> None:
|
|
140
|
-
"""Apply idempotent schema additions (delegated to broker_security)."""
|
|
141
|
-
conn = self._conn()
|
|
142
|
-
try:
|
|
143
|
-
apply_security_schema(conn)
|
|
144
|
-
except sqlite3.Error as exc:
|
|
145
|
-
logger.debug("mesh schema update skipped: %s", exc)
|
|
146
|
-
finally:
|
|
147
|
-
conn.close()
|
|
148
|
-
|
|
149
|
-
def _next_fencing_token(self) -> int:
|
|
150
|
-
"""Return the next monotonically-increasing fencing token."""
|
|
151
|
-
with self._fencing_lock:
|
|
152
|
-
self._fencing_counter += 1
|
|
153
|
-
return self._fencing_counter
|
|
154
|
-
|
|
155
|
-
# -- Connection helper --
|
|
156
|
-
|
|
157
|
-
def _conn(self) -> sqlite3.Connection:
|
|
158
|
-
conn = sqlite3.connect(
|
|
159
|
-
self._db_path,
|
|
160
|
-
timeout=_WRITE_BUSY_TIMEOUT_MS / 1000,
|
|
161
|
-
)
|
|
162
|
-
conn.execute(f"PRAGMA busy_timeout={_WRITE_BUSY_TIMEOUT_MS}")
|
|
163
|
-
conn.row_factory = sqlite3.Row
|
|
164
|
-
return conn
|
|
165
|
-
|
|
166
|
-
@staticmethod
|
|
167
|
-
def _is_transient_lock(exc: sqlite3.OperationalError) -> bool:
|
|
168
|
-
message = str(exc).lower()
|
|
169
|
-
return "database is locked" in message or "database is busy" in message
|
|
170
|
-
|
|
171
|
-
def _write_with_retry(self, operation: Callable[[sqlite3.Connection], _T]) -> _T:
|
|
172
|
-
"""Run one idempotent mesh mutation with a bounded SQLite retry budget."""
|
|
173
|
-
last_error: sqlite3.OperationalError | None = None
|
|
174
|
-
for attempt in range(_WRITE_RETRY_ATTEMPTS):
|
|
175
|
-
conn = self._conn()
|
|
176
|
-
try:
|
|
177
|
-
return operation(conn)
|
|
178
|
-
except sqlite3.OperationalError as exc:
|
|
179
|
-
if not self._is_transient_lock(exc):
|
|
180
|
-
raise
|
|
181
|
-
last_error = exc
|
|
182
|
-
try:
|
|
183
|
-
conn.rollback()
|
|
184
|
-
except sqlite3.Error:
|
|
185
|
-
pass
|
|
186
|
-
finally:
|
|
187
|
-
conn.close()
|
|
188
|
-
|
|
189
|
-
if attempt < _WRITE_RETRY_ATTEMPTS - 1:
|
|
190
|
-
time.sleep(_WRITE_RETRY_BASE_SECONDS * (2 ** attempt))
|
|
191
|
-
|
|
192
|
-
assert last_error is not None
|
|
193
|
-
raise last_error
|
|
194
|
-
|
|
195
|
-
# -- Peers --
|
|
196
|
-
|
|
197
|
-
def register_peer(self, session_id: str, summary: str = "",
|
|
198
|
-
host: str = "", port: int = 0,
|
|
199
|
-
project_path: str = "", agent_type: str = "unknown",
|
|
200
|
-
profile_id: str = "default") -> dict:
|
|
201
|
-
def _register(conn: sqlite3.Connection) -> dict:
|
|
202
|
-
now = datetime.now(timezone.utc).isoformat()
|
|
203
|
-
effective_host = host or self._host
|
|
204
|
-
# Idempotent within the tenant: update if same session_id exists
|
|
205
|
-
# under this profile. A session_id in another tenant is a different
|
|
206
|
-
# peer and must not be adopted here.
|
|
207
|
-
existing = conn.execute(
|
|
208
|
-
"SELECT peer_id FROM mesh_peers WHERE session_id = ? AND profile_id = ?",
|
|
209
|
-
(session_id, profile_id),
|
|
210
|
-
).fetchone()
|
|
211
|
-
if existing:
|
|
212
|
-
peer_id = existing["peer_id"]
|
|
213
|
-
is_new_peer = False
|
|
214
|
-
conn.execute(
|
|
215
|
-
"UPDATE mesh_peers SET summary=?, host=?, port=?, last_heartbeat=?, "
|
|
216
|
-
"status='active', project_path=?, agent_type=? "
|
|
217
|
-
"WHERE peer_id=? AND profile_id=?",
|
|
218
|
-
(summary, effective_host, port, now, project_path, agent_type,
|
|
219
|
-
peer_id, profile_id),
|
|
220
|
-
)
|
|
221
|
-
else:
|
|
222
|
-
peer_id = str(uuid.uuid4())[:12]
|
|
223
|
-
is_new_peer = True
|
|
224
|
-
conn.execute(
|
|
225
|
-
"INSERT INTO mesh_peers (peer_id, session_id, summary, status, host, port, "
|
|
226
|
-
"registered_at, last_heartbeat, project_path, agent_type, profile_id) "
|
|
227
|
-
"VALUES (?, ?, ?, 'active', ?, ?, ?, ?, ?, ?, ?)",
|
|
228
|
-
(peer_id, session_id, summary, effective_host, port, now, now,
|
|
229
|
-
project_path, agent_type, profile_id),
|
|
230
|
-
)
|
|
231
|
-
self._log_event(conn, "peer_registered", peer_id, {
|
|
232
|
-
"session_id": session_id, "project_path": project_path,
|
|
233
|
-
}, profile_id=profile_id)
|
|
234
|
-
conn.commit()
|
|
235
|
-
|
|
236
|
-
# SEC-4 + P0: mint & RETURN the per-peer HMAC key ONLY on first
|
|
237
|
-
# registration. Re-registration MUST NOT re-export the key — otherwise
|
|
238
|
-
# a fleet-secret holder who learns a session_id (via /mesh/peers) could
|
|
239
|
-
# re-register as the victim and steal their peer_key (identity theft).
|
|
240
|
-
# The legitimate owner keeps the key it received at first registration.
|
|
241
|
-
# v3.4.6: Deliver pending broadcast/project messages on registration
|
|
242
|
-
pending = self._get_pending_for_peer(conn, peer_id, project_path, profile_id)
|
|
243
|
-
result = {"peer_id": peer_id, "ok": True, "pending_messages": len(pending)}
|
|
244
|
-
if is_new_peer:
|
|
245
|
-
result["peer_key"] = get_or_create_peer_key(conn, peer_id, profile_id)
|
|
246
|
-
return result
|
|
247
|
-
|
|
248
|
-
return self._write_with_retry(_register)
|
|
249
|
-
|
|
250
|
-
def deregister_peer(self, peer_id: str, profile_id: str = "default") -> dict:
|
|
251
|
-
def _deregister(conn: sqlite3.Connection) -> dict:
|
|
252
|
-
row = conn.execute(
|
|
253
|
-
"SELECT 1 FROM mesh_peers WHERE peer_id=? AND profile_id=?",
|
|
254
|
-
(peer_id, profile_id),
|
|
255
|
-
).fetchone()
|
|
256
|
-
if not row:
|
|
257
|
-
return {"ok": False, "error": "peer not found"}
|
|
258
|
-
conn.execute(
|
|
259
|
-
"DELETE FROM mesh_peers WHERE peer_id=? AND profile_id=?",
|
|
260
|
-
(peer_id, profile_id),
|
|
261
|
-
)
|
|
262
|
-
self._log_event(conn, "peer_deregistered", peer_id, profile_id=profile_id)
|
|
263
|
-
conn.commit()
|
|
264
|
-
return {"ok": True}
|
|
265
|
-
|
|
266
|
-
return self._write_with_retry(_deregister)
|
|
267
|
-
|
|
268
|
-
def heartbeat(self, peer_id: str, profile_id: str = "default") -> dict:
|
|
269
|
-
def _heartbeat(conn: sqlite3.Connection) -> dict:
|
|
270
|
-
now = datetime.now(timezone.utc).isoformat()
|
|
271
|
-
cursor = conn.execute(
|
|
272
|
-
"UPDATE mesh_peers SET last_heartbeat=?, status='active' "
|
|
273
|
-
"WHERE peer_id=? AND profile_id=?",
|
|
274
|
-
(now, peer_id, profile_id),
|
|
275
|
-
)
|
|
276
|
-
if cursor.rowcount == 0:
|
|
277
|
-
return {"ok": False, "error": "peer not found"}
|
|
278
|
-
conn.commit()
|
|
279
|
-
return {"ok": True}
|
|
280
|
-
|
|
281
|
-
return self._write_with_retry(_heartbeat)
|
|
282
|
-
|
|
283
|
-
def update_summary(self, peer_id: str, summary: str,
|
|
284
|
-
profile_id: str = "default") -> dict:
|
|
285
|
-
def _update_summary(conn: sqlite3.Connection) -> dict:
|
|
286
|
-
cursor = conn.execute(
|
|
287
|
-
"UPDATE mesh_peers SET summary=? WHERE peer_id=? AND profile_id=?",
|
|
288
|
-
(summary, peer_id, profile_id),
|
|
289
|
-
)
|
|
290
|
-
if cursor.rowcount == 0:
|
|
291
|
-
return {"ok": False, "error": "peer not found"}
|
|
292
|
-
conn.commit()
|
|
293
|
-
return {"ok": True}
|
|
294
|
-
|
|
295
|
-
return self._write_with_retry(_update_summary)
|
|
296
|
-
|
|
297
|
-
def list_peers(self, profile_id: str = "default") -> list[dict]:
|
|
298
|
-
conn = self._conn()
|
|
299
|
-
try:
|
|
300
|
-
rows = conn.execute(
|
|
301
|
-
"SELECT peer_id, session_id, summary, status, host, port, "
|
|
302
|
-
"registered_at, last_heartbeat, project_path, agent_type, profile_id "
|
|
303
|
-
"FROM mesh_peers WHERE profile_id=? ORDER BY last_heartbeat DESC",
|
|
304
|
-
(profile_id,),
|
|
305
|
-
).fetchall()
|
|
306
|
-
return [dict(r) for r in rows]
|
|
307
|
-
finally:
|
|
308
|
-
conn.close()
|
|
309
|
-
|
|
310
|
-
# -- Messages --
|
|
311
|
-
|
|
312
|
-
def send_message(self, from_peer: str, to_peer: str, content: str,
|
|
313
|
-
msg_type: str = "text", project_path: str = "",
|
|
314
|
-
profile_id: str = "default",
|
|
315
|
-
operation_id: str | None = None) -> dict:
|
|
316
|
-
# Guard: 4KB message size cap
|
|
317
|
-
if len(content) > MAX_MESSAGE_SIZE:
|
|
318
|
-
return {"ok": False, "error": f"message too large ({len(content)} bytes, max {MAX_MESSAGE_SIZE}). "
|
|
319
|
-
"Mesh messages are notifications — reference a file path instead."}
|
|
320
|
-
|
|
321
|
-
# Remote delivery is an external side effect, so do it outside the
|
|
322
|
-
# retry envelope. Local writes below are retried as a whole short
|
|
323
|
-
# transaction when another daemon-owned operation has SQLite's writer.
|
|
324
|
-
# Hold the lock only for the membership check, not for the HTTP call.
|
|
325
|
-
# A remote peer only counts if it belongs to this tenant (profile).
|
|
326
|
-
with self._remote_peers_lock:
|
|
327
|
-
remote_info = self._remote_peers.get(to_peer)
|
|
328
|
-
is_remote = (
|
|
329
|
-
remote_info is not None
|
|
330
|
-
and remote_info.get("profile_id", "default") == profile_id
|
|
331
|
-
)
|
|
332
|
-
if is_remote and self._sync_client:
|
|
333
|
-
return self._sync_client.send_to_remote(to_peer, {
|
|
334
|
-
"from_peer": from_peer,
|
|
335
|
-
"to": to_peer,
|
|
336
|
-
"content": content,
|
|
337
|
-
"type": msg_type,
|
|
338
|
-
"profile_id": profile_id,
|
|
339
|
-
})
|
|
340
|
-
|
|
341
|
-
def _send(conn: sqlite3.Connection) -> dict:
|
|
342
|
-
# Derive locals fresh on EVERY call. A nonlocal mutation of to_peer
|
|
343
|
-
# persisted across _write_with_retry attempts: a 'project:' address
|
|
344
|
-
# rewritten to 'project' on the first try then misrouted to the
|
|
345
|
-
# direct-peer branch on retry ("recipient peer not found").
|
|
346
|
-
_to_peer = to_peer
|
|
347
|
-
_project_path = project_path
|
|
348
|
-
now = datetime.now(timezone.utc).isoformat()
|
|
349
|
-
expires_at = self._compute_expires(now)
|
|
350
|
-
|
|
351
|
-
# Idempotency: return original result for a repeated operation_id.
|
|
352
|
-
if operation_id:
|
|
353
|
-
op = conn.execute(
|
|
354
|
-
"SELECT message_id FROM mesh_sent_ops WHERE operation_id=?", (operation_id,)
|
|
355
|
-
).fetchone()
|
|
356
|
-
if op:
|
|
357
|
-
return {"ok": True, "id": op["message_id"],
|
|
358
|
-
"idempotent": True, "operation_id": operation_id}
|
|
359
|
-
|
|
360
|
-
# Identity binding: cross-profile impersonation guard.
|
|
361
|
-
cross_profile_err = check_cross_profile_sender(conn, from_peer, profile_id)
|
|
362
|
-
if cross_profile_err is not None:
|
|
363
|
-
return cross_profile_err
|
|
364
|
-
|
|
365
|
-
# Determine target type
|
|
366
|
-
if _to_peer == "broadcast":
|
|
367
|
-
target_type = "broadcast"
|
|
368
|
-
elif _to_peer.startswith("project:"):
|
|
369
|
-
target_type = "project"
|
|
370
|
-
_project_path = _to_peer[len("project:"):]
|
|
371
|
-
_to_peer = "project"
|
|
372
|
-
else:
|
|
373
|
-
target_type = "peer"
|
|
374
|
-
# Verify recipient exists WITHIN this tenant for direct messages.
|
|
375
|
-
# A peer_id in another profile is not a valid recipient here.
|
|
376
|
-
if not conn.execute(
|
|
377
|
-
"SELECT 1 FROM mesh_peers WHERE peer_id=? AND profile_id=?",
|
|
378
|
-
(_to_peer, profile_id),
|
|
379
|
-
).fetchone():
|
|
380
|
-
return {"ok": False, "error": "recipient peer not found"}
|
|
381
|
-
|
|
382
|
-
# Enforce per-target queue cap (per tenant)
|
|
383
|
-
if target_type in ("broadcast", "project"):
|
|
384
|
-
count = conn.execute(
|
|
385
|
-
"SELECT COUNT(*) FROM mesh_messages "
|
|
386
|
-
"WHERE profile_id=? AND target_type=? AND project_path=? AND read=0",
|
|
387
|
-
(profile_id, target_type, _project_path),
|
|
388
|
-
).fetchone()[0]
|
|
389
|
-
if count >= MAX_QUEUED_PER_TARGET:
|
|
390
|
-
# Delete oldest to make room
|
|
391
|
-
conn.execute(
|
|
392
|
-
"DELETE FROM mesh_messages WHERE id IN ("
|
|
393
|
-
" SELECT id FROM mesh_messages "
|
|
394
|
-
" WHERE profile_id=? AND target_type=? AND project_path=? AND read=0 "
|
|
395
|
-
" ORDER BY created_at ASC LIMIT ?)",
|
|
396
|
-
(profile_id, target_type, _project_path,
|
|
397
|
-
count - MAX_QUEUED_PER_TARGET + 1),
|
|
398
|
-
)
|
|
399
|
-
|
|
400
|
-
_content = scrub_message_content(content) # 3a-2: redact before storage
|
|
401
|
-
cursor = conn.execute(
|
|
402
|
-
"INSERT INTO mesh_messages (from_peer, to_peer, msg_type, content, read, "
|
|
403
|
-
"created_at, expires_at, target_type, project_path, profile_id) "
|
|
404
|
-
"VALUES (?, ?, ?, ?, 0, ?, ?, ?, ?, ?)",
|
|
405
|
-
(from_peer, _to_peer, msg_type, _content, now, expires_at,
|
|
406
|
-
target_type, _project_path, profile_id),
|
|
407
|
-
)
|
|
408
|
-
msg_id = cursor.lastrowid
|
|
409
|
-
|
|
410
|
-
if operation_id:
|
|
411
|
-
conn.execute("INSERT OR IGNORE INTO mesh_sent_ops (operation_id, message_id, created_at) VALUES (?, ?, ?)", (operation_id, msg_id, now))
|
|
412
|
-
|
|
413
|
-
self._log_event(conn, "message_sent", from_peer or "system", {
|
|
414
|
-
"to": _to_peer, "target_type": target_type, "project": _project_path,
|
|
415
|
-
}, profile_id=profile_id)
|
|
416
|
-
conn.commit()
|
|
417
|
-
return {"ok": True, "id": msg_id, "target_type": target_type,
|
|
418
|
-
"expires_at": expires_at}
|
|
419
|
-
|
|
420
|
-
return self._write_with_retry(_send)
|
|
421
|
-
|
|
422
|
-
def get_inbox(self, peer_id: str, project_path: str = "",
|
|
423
|
-
profile_id: str = "default") -> list[dict]:
|
|
424
|
-
"""Get all messages for this peer: direct + broadcast + project.
|
|
425
|
-
|
|
426
|
-
Scoped to the peer's tenant (profile_id): a peer never sees another
|
|
427
|
-
tenant's direct, broadcast, or project traffic.
|
|
428
|
-
"""
|
|
429
|
-
conn = self._conn()
|
|
430
|
-
try:
|
|
431
|
-
now = datetime.now(timezone.utc).isoformat()
|
|
432
|
-
# Direct messages to this peer
|
|
433
|
-
# v3.6.12 (mesh-3): only UNREAD direct messages — was returning read
|
|
434
|
-
# ones too, so every poll re-listed already-read messages until the
|
|
435
|
-
# 24h cleanup (broadcast/project already filter unread via mesh_reads).
|
|
436
|
-
direct = conn.execute(
|
|
437
|
-
"SELECT id, from_peer, to_peer, msg_type, content, read, created_at, "
|
|
438
|
-
"target_type, project_path FROM mesh_messages "
|
|
439
|
-
"WHERE profile_id=? AND to_peer=? AND target_type='peer' "
|
|
440
|
-
"AND COALESCE(read, 0) = 0 "
|
|
441
|
-
"AND (expires_at IS NULL OR expires_at > ?) "
|
|
442
|
-
"ORDER BY created_at DESC LIMIT 100",
|
|
443
|
-
(profile_id, peer_id, now),
|
|
444
|
-
).fetchall()
|
|
445
|
-
|
|
446
|
-
# Broadcast messages not from this peer and not yet read by this peer
|
|
447
|
-
broadcast = conn.execute(
|
|
448
|
-
"SELECT m.id, m.from_peer, m.to_peer, m.msg_type, m.content, "
|
|
449
|
-
"CASE WHEN r.peer_id IS NOT NULL THEN 1 ELSE 0 END AS read, "
|
|
450
|
-
"m.created_at, m.target_type, m.project_path "
|
|
451
|
-
"FROM mesh_messages m "
|
|
452
|
-
"LEFT JOIN mesh_reads r ON m.id = r.message_id AND r.peer_id = ? "
|
|
453
|
-
"WHERE m.profile_id=? AND m.target_type='broadcast' AND m.from_peer != ? "
|
|
454
|
-
"AND r.peer_id IS NULL "
|
|
455
|
-
"AND (m.expires_at IS NULL OR m.expires_at > ?) "
|
|
456
|
-
"ORDER BY m.created_at DESC LIMIT 50",
|
|
457
|
-
(peer_id, profile_id, peer_id, now),
|
|
458
|
-
).fetchall()
|
|
459
|
-
|
|
460
|
-
# Project messages for my project, not from me, not yet read
|
|
461
|
-
project_msgs = []
|
|
462
|
-
if project_path:
|
|
463
|
-
project_msgs = conn.execute(
|
|
464
|
-
"SELECT m.id, m.from_peer, m.to_peer, m.msg_type, m.content, "
|
|
465
|
-
"CASE WHEN r.peer_id IS NOT NULL THEN 1 ELSE 0 END AS read, "
|
|
466
|
-
"m.created_at, m.target_type, m.project_path "
|
|
467
|
-
"FROM mesh_messages m "
|
|
468
|
-
"LEFT JOIN mesh_reads r ON m.id = r.message_id AND r.peer_id = ? "
|
|
469
|
-
"WHERE m.profile_id=? AND m.target_type='project' "
|
|
470
|
-
"AND m.project_path=? AND m.from_peer != ? "
|
|
471
|
-
"AND r.peer_id IS NULL "
|
|
472
|
-
"AND (m.expires_at IS NULL OR m.expires_at > ?) "
|
|
473
|
-
"ORDER BY m.created_at DESC LIMIT 50",
|
|
474
|
-
(peer_id, profile_id, project_path, peer_id, now),
|
|
475
|
-
).fetchall()
|
|
476
|
-
|
|
477
|
-
all_msgs = [dict(r) for r in direct] + [dict(r) for r in broadcast] + [dict(r) for r in project_msgs]
|
|
478
|
-
# Sort by created_at descending
|
|
479
|
-
all_msgs.sort(key=lambda m: m.get("created_at", ""), reverse=True)
|
|
480
|
-
return all_msgs[:100]
|
|
481
|
-
finally:
|
|
482
|
-
conn.close()
|
|
483
|
-
|
|
484
|
-
def mark_read(self, peer_id: str, message_ids: list[int],
|
|
485
|
-
profile_id: str = "default") -> dict:
|
|
486
|
-
def _mark_read(conn: sqlite3.Connection) -> dict:
|
|
487
|
-
if not message_ids:
|
|
488
|
-
return {"ok": True, "marked": 0}
|
|
489
|
-
now = datetime.now(timezone.utc).isoformat()
|
|
490
|
-
ph = ",".join("?" * len(message_ids))
|
|
491
|
-
# One batched read of target types (tenant-scoped), then batched
|
|
492
|
-
# writes — was 2N round-trips per N messages.
|
|
493
|
-
rows = conn.execute(
|
|
494
|
-
f"SELECT id, target_type FROM mesh_messages "
|
|
495
|
-
f"WHERE id IN ({ph}) AND profile_id=?",
|
|
496
|
-
(*message_ids, profile_id),
|
|
497
|
-
).fetchall()
|
|
498
|
-
direct_ids = [r["id"] for r in rows if r["target_type"] == "peer"]
|
|
499
|
-
shared_ids = [r["id"] for r in rows if r["target_type"] != "peer"]
|
|
500
|
-
if direct_ids:
|
|
501
|
-
dph = ",".join("?" * len(direct_ids))
|
|
502
|
-
conn.execute(
|
|
503
|
-
f"UPDATE mesh_messages SET read=1 "
|
|
504
|
-
f"WHERE id IN ({dph}) AND to_peer=? AND profile_id=?",
|
|
505
|
-
(*direct_ids, peer_id, profile_id),
|
|
506
|
-
)
|
|
507
|
-
if shared_ids:
|
|
508
|
-
conn.executemany(
|
|
509
|
-
"INSERT OR IGNORE INTO mesh_reads (message_id, peer_id, read_at) "
|
|
510
|
-
"VALUES (?, ?, ?)",
|
|
511
|
-
[(mid, peer_id, now) for mid in shared_ids],
|
|
512
|
-
)
|
|
513
|
-
conn.commit()
|
|
514
|
-
return {"ok": True, "marked": len(message_ids)}
|
|
515
|
-
|
|
516
|
-
return self._write_with_retry(_mark_read)
|
|
517
|
-
|
|
518
|
-
# -- State --
|
|
519
|
-
|
|
520
|
-
def get_state(self, profile_id: str = "default") -> dict:
|
|
521
|
-
conn = self._conn()
|
|
522
|
-
try:
|
|
523
|
-
rows = conn.execute(
|
|
524
|
-
"SELECT key, value, set_by, updated_at FROM mesh_state WHERE profile_id=?",
|
|
525
|
-
(profile_id,),
|
|
526
|
-
).fetchall()
|
|
527
|
-
return {r["key"]: {"value": r["value"], "set_by": r["set_by"], "updated_at": r["updated_at"]} for r in rows}
|
|
528
|
-
finally:
|
|
529
|
-
conn.close()
|
|
530
|
-
|
|
531
|
-
def set_state(self, key: str, value: str, set_by: str,
|
|
532
|
-
profile_id: str = "default",
|
|
533
|
-
expected_revision: int | None = None) -> dict:
|
|
534
|
-
"""Set coordination state; rejects secrets and enforces optimistic revision."""
|
|
535
|
-
secret_err = reject_secret_state(key, value)
|
|
536
|
-
if secret_err is not None:
|
|
537
|
-
return secret_err
|
|
538
|
-
|
|
539
|
-
def _set_state(conn: sqlite3.Connection) -> dict:
|
|
540
|
-
now = datetime.now(timezone.utc).isoformat()
|
|
541
|
-
if expected_revision is not None:
|
|
542
|
-
row = conn.execute(
|
|
543
|
-
"SELECT revision FROM mesh_state WHERE profile_id=? AND key=?",
|
|
544
|
-
(profile_id, key),
|
|
545
|
-
).fetchone()
|
|
546
|
-
current = row["revision"] if row else 0
|
|
547
|
-
if current != expected_revision:
|
|
548
|
-
return {"ok": False,
|
|
549
|
-
"error": f"revision mismatch: expected {expected_revision}, current {current}"}
|
|
550
|
-
conn.execute(
|
|
551
|
-
"INSERT INTO mesh_state"
|
|
552
|
-
" (profile_id, key, value, set_by, updated_at, revision, origin_node)"
|
|
553
|
-
" VALUES (?, ?, ?, ?, ?, 1, '')"
|
|
554
|
-
" ON CONFLICT(profile_id, key) DO UPDATE SET value=excluded.value,"
|
|
555
|
-
" set_by=excluded.set_by, updated_at=excluded.updated_at,"
|
|
556
|
-
" revision=COALESCE(mesh_state.revision, 0) + 1,"
|
|
557
|
-
# 3c-1: a local write resets provenance to THIS node ('' = local).
|
|
558
|
-
# Without this, a local set_state after a remote merge would keep
|
|
559
|
-
# the remote's origin_node and re-export under the wrong identity
|
|
560
|
-
# → permanent same-(revision,node) divergence (audit P0-1).
|
|
561
|
-
" origin_node=''",
|
|
562
|
-
(profile_id, key, value, set_by, now),
|
|
563
|
-
)
|
|
564
|
-
conn.commit()
|
|
565
|
-
return {"ok": True}
|
|
566
|
-
|
|
567
|
-
return self._write_with_retry(_set_state)
|
|
568
|
-
|
|
569
|
-
def get_state_key(self, key: str, profile_id: str = "default") -> dict | None:
|
|
570
|
-
conn = self._conn()
|
|
571
|
-
try:
|
|
572
|
-
row = conn.execute(
|
|
573
|
-
"SELECT key, value, set_by, updated_at, "
|
|
574
|
-
"COALESCE(revision, 0) AS revision "
|
|
575
|
-
"FROM mesh_state WHERE profile_id=? AND key=?",
|
|
576
|
-
(profile_id, key),
|
|
577
|
-
).fetchone()
|
|
578
|
-
return dict(row) if row else None
|
|
579
|
-
finally:
|
|
580
|
-
conn.close()
|
|
581
|
-
|
|
582
|
-
# -- Locks --
|
|
583
|
-
|
|
584
|
-
def lock_action(self, file_path: str, locked_by: str, action: str,
|
|
585
|
-
profile_id: str = "default") -> dict:
|
|
586
|
-
if action == "query":
|
|
587
|
-
conn = self._conn()
|
|
588
|
-
try:
|
|
589
|
-
row = conn.execute(
|
|
590
|
-
"SELECT locked_by, locked_at, COALESCE(fencing_token, 0) AS fencing_token"
|
|
591
|
-
" FROM mesh_locks WHERE profile_id=? AND file_path=?",
|
|
592
|
-
(profile_id, file_path),
|
|
593
|
-
).fetchone()
|
|
594
|
-
if row:
|
|
595
|
-
return {"locked": True, "by": row["locked_by"],
|
|
596
|
-
"since": row["locked_at"], "fencing_token": row["fencing_token"]}
|
|
597
|
-
return {"locked": False}
|
|
598
|
-
finally:
|
|
599
|
-
conn.close()
|
|
600
|
-
|
|
601
|
-
if action not in {"acquire", "release"}:
|
|
602
|
-
return {"ok": False, "error": f"unknown action: {action}"}
|
|
603
|
-
|
|
604
|
-
def _lock_action(conn: sqlite3.Connection) -> dict:
|
|
605
|
-
now = datetime.now(timezone.utc).isoformat()
|
|
606
|
-
|
|
607
|
-
if action == "acquire":
|
|
608
|
-
existing = conn.execute(
|
|
609
|
-
"SELECT locked_by, locked_at, expires_at FROM mesh_locks "
|
|
610
|
-
"WHERE profile_id=? AND file_path=?",
|
|
611
|
-
(profile_id, file_path),
|
|
612
|
-
).fetchone()
|
|
613
|
-
# M-02: another peer's lock only blocks while it is still live.
|
|
614
|
-
# An expired lock — or a legacy never-expires sentinel left by a
|
|
615
|
-
# crashed session — is treated as free, so a stale lock can never
|
|
616
|
-
# deadlock a path forever.
|
|
617
|
-
if existing and existing["locked_by"] != locked_by:
|
|
618
|
-
exp = existing["expires_at"]
|
|
619
|
-
still_live = bool(exp) and exp != _NEVER_EXPIRES and exp > now
|
|
620
|
-
if still_live:
|
|
621
|
-
return {"locked": True, "by": existing["locked_by"],
|
|
622
|
-
"since": existing["locked_at"]}
|
|
623
|
-
lock_expires = (
|
|
624
|
-
datetime.fromisoformat(now) + timedelta(hours=LOCK_TTL_HOURS)
|
|
625
|
-
).isoformat()
|
|
626
|
-
token = self._next_fencing_token()
|
|
627
|
-
conn.execute(
|
|
628
|
-
"INSERT INTO mesh_locks (profile_id, file_path, locked_by, locked_at,"
|
|
629
|
-
" expires_at, fencing_token) VALUES (?, ?, ?, ?, ?, ?)"
|
|
630
|
-
" ON CONFLICT(profile_id, file_path) DO UPDATE SET locked_by=excluded.locked_by,"
|
|
631
|
-
" locked_at=excluded.locked_at, expires_at=excluded.expires_at,"
|
|
632
|
-
" fencing_token=excluded.fencing_token",
|
|
633
|
-
(profile_id, file_path, locked_by, now, lock_expires, token),
|
|
634
|
-
)
|
|
635
|
-
conn.commit()
|
|
636
|
-
return {"ok": True, "action": "acquired",
|
|
637
|
-
"expires_at": lock_expires, "fencing_token": token}
|
|
638
|
-
|
|
639
|
-
elif action == "release":
|
|
640
|
-
# v3.6.12 (mesh-2): report whether we actually released. The
|
|
641
|
-
# DELETE is correctly owner-scoped, but it previously returned
|
|
642
|
-
# released=ok:true even when a NON-owner released nothing.
|
|
643
|
-
cur = conn.execute(
|
|
644
|
-
"DELETE FROM mesh_locks WHERE profile_id=? AND file_path=? AND locked_by=?",
|
|
645
|
-
(profile_id, file_path, locked_by),
|
|
646
|
-
)
|
|
647
|
-
conn.commit()
|
|
648
|
-
if cur.rowcount and cur.rowcount > 0:
|
|
649
|
-
return {"ok": True, "action": "released"}
|
|
650
|
-
return {"ok": False, "action": "not_released",
|
|
651
|
-
"error": "no lock held by this peer for that file"}
|
|
652
|
-
|
|
653
|
-
raise AssertionError("validated action was not handled")
|
|
654
|
-
|
|
655
|
-
return self._write_with_retry(_lock_action)
|
|
656
|
-
|
|
657
|
-
def validate_lock_fence(self, file_path: str, fencing_token: int,
|
|
658
|
-
profile_id: str = "default") -> dict:
|
|
659
|
-
"""Reject a stale fencing token; allow the current one."""
|
|
660
|
-
conn = self._conn()
|
|
661
|
-
try:
|
|
662
|
-
return validate_lock_fence_query(conn, file_path, fencing_token, profile_id)
|
|
663
|
-
finally:
|
|
664
|
-
conn.close()
|
|
665
|
-
|
|
666
|
-
# -- Helpers (v3.4.6) --
|
|
667
|
-
|
|
668
|
-
@staticmethod
|
|
669
|
-
def _compute_expires(now_iso: str) -> str:
|
|
670
|
-
"""Compute expiry timestamp MESSAGE_TTL_HOURS from now."""
|
|
671
|
-
from datetime import timedelta
|
|
672
|
-
now = datetime.fromisoformat(now_iso)
|
|
673
|
-
return (now + timedelta(hours=MESSAGE_TTL_HOURS)).isoformat()
|
|
674
|
-
|
|
675
|
-
def _get_pending_for_peer(self, conn: sqlite3.Connection,
|
|
676
|
-
peer_id: str, project_path: str,
|
|
677
|
-
profile_id: str = "default") -> list[dict]:
|
|
678
|
-
"""Get unread broadcast/project messages for a newly registered peer
|
|
679
|
-
within this tenant."""
|
|
680
|
-
now = datetime.now(timezone.utc).isoformat()
|
|
681
|
-
rows = conn.execute(
|
|
682
|
-
"SELECT m.id, m.from_peer, m.content, m.target_type, m.project_path, m.created_at "
|
|
683
|
-
"FROM mesh_messages m "
|
|
684
|
-
"LEFT JOIN mesh_reads r ON m.id = r.message_id AND r.peer_id = ? "
|
|
685
|
-
"WHERE m.profile_id = ? AND r.peer_id IS NULL AND m.from_peer != ? "
|
|
686
|
-
"AND (m.expires_at IS NULL OR m.expires_at > ?) "
|
|
687
|
-
"AND (m.target_type = 'broadcast' "
|
|
688
|
-
" OR (m.target_type = 'project' AND m.project_path = ?)) "
|
|
689
|
-
"ORDER BY m.created_at DESC LIMIT 50",
|
|
690
|
-
(peer_id, profile_id, peer_id, now, project_path),
|
|
691
|
-
).fetchall()
|
|
692
|
-
return [dict(r) for r in rows]
|
|
693
|
-
|
|
694
|
-
def get_pending(self, peer_id: str, project_path: str = "",
|
|
695
|
-
profile_id: str = "default") -> list[dict]:
|
|
696
|
-
"""Public API to get pending broadcast/project messages."""
|
|
697
|
-
conn = self._conn()
|
|
698
|
-
try:
|
|
699
|
-
return self._get_pending_for_peer(conn, peer_id, project_path, profile_id)
|
|
700
|
-
finally:
|
|
701
|
-
conn.close()
|
|
702
|
-
|
|
703
|
-
# -- Events --
|
|
704
|
-
|
|
705
|
-
def get_events(self, limit: int = 100, profile_id: str = "default") -> list[dict]:
|
|
706
|
-
conn = self._conn()
|
|
707
|
-
try:
|
|
708
|
-
rows = conn.execute(
|
|
709
|
-
"SELECT id, event_type, payload, emitted_by, created_at "
|
|
710
|
-
"FROM mesh_events WHERE profile_id=? ORDER BY id DESC LIMIT ?",
|
|
711
|
-
(profile_id, limit),
|
|
712
|
-
).fetchall()
|
|
713
|
-
return [dict(r) for r in rows]
|
|
714
|
-
finally:
|
|
715
|
-
conn.close()
|
|
716
|
-
|
|
717
|
-
def _log_event(self, conn: sqlite3.Connection, event_type: str,
|
|
718
|
-
emitted_by: str, payload: dict | None = None,
|
|
719
|
-
profile_id: str = "default") -> None:
|
|
720
|
-
import json as _json
|
|
721
|
-
now = datetime.now(timezone.utc).isoformat()
|
|
722
|
-
conn.execute(
|
|
723
|
-
"INSERT INTO mesh_events (event_type, payload, emitted_by, created_at, profile_id) "
|
|
724
|
-
"VALUES (?, ?, ?, ?, ?)",
|
|
725
|
-
(event_type, _json.dumps(payload or {}), emitted_by, now, profile_id),
|
|
726
|
-
)
|
|
727
|
-
|
|
728
|
-
# -- Status --
|
|
729
|
-
|
|
730
|
-
def get_status(self, profile_id: str = "default") -> dict:
|
|
731
|
-
conn = self._conn()
|
|
732
|
-
try:
|
|
733
|
-
peer_count = conn.execute(
|
|
734
|
-
"SELECT COUNT(*) FROM mesh_peers WHERE profile_id=? AND status='active'",
|
|
735
|
-
(profile_id,),
|
|
736
|
-
).fetchone()[0]
|
|
737
|
-
return {
|
|
738
|
-
"broker_up": True,
|
|
739
|
-
"peer_count": peer_count,
|
|
740
|
-
"uptime_s": round(time.monotonic() - self._started_at),
|
|
741
|
-
}
|
|
742
|
-
finally:
|
|
743
|
-
conn.close()
|
|
744
|
-
|
|
745
|
-
# -- Cleanup --
|
|
746
|
-
|
|
747
|
-
def _cleanup_loop(self) -> None:
|
|
748
|
-
"""Background cleanup: mark stale peers, delete old messages."""
|
|
749
|
-
while not self._stop_event.is_set():
|
|
750
|
-
self._stop_event.wait(300) # Every 5 min
|
|
751
|
-
if self._stop_event.is_set():
|
|
752
|
-
break
|
|
753
|
-
try:
|
|
754
|
-
self._run_cleanup()
|
|
755
|
-
except Exception as exc:
|
|
756
|
-
logger.debug("Mesh cleanup error: %s", exc)
|
|
757
|
-
|
|
758
|
-
def _run_cleanup(self) -> None:
|
|
759
|
-
def _cleanup(conn: sqlite3.Connection) -> None:
|
|
760
|
-
# Precompute ISO cutoffs in Python and compare the stored ISO strings
|
|
761
|
-
# directly. ISO-8601 UTC timestamps sort lexicographically, so a bare
|
|
762
|
-
# `col < ?` is both correct AND sargable — the datetime() wrapper
|
|
763
|
-
# previously forced a full table scan on every 5-minute cleanup.
|
|
764
|
-
now = datetime.now(timezone.utc)
|
|
765
|
-
now_iso = now.isoformat()
|
|
766
|
-
five_min = (now - timedelta(minutes=5)).isoformat()
|
|
767
|
-
thirty_min = (now - timedelta(minutes=30)).isoformat()
|
|
768
|
-
day_ago = (now - timedelta(hours=24)).isoformat()
|
|
769
|
-
week_ago = (now - timedelta(days=7)).isoformat()
|
|
770
|
-
# Mark stale peers (no heartbeat for 5 min)
|
|
771
|
-
conn.execute(
|
|
772
|
-
"UPDATE mesh_peers SET status='stale' "
|
|
773
|
-
"WHERE status='active' AND last_heartbeat < ?",
|
|
774
|
-
(five_min,),
|
|
775
|
-
)
|
|
776
|
-
# Delete dead peers (stale > 30 min)
|
|
777
|
-
conn.execute(
|
|
778
|
-
"UPDATE mesh_peers SET status='dead' "
|
|
779
|
-
"WHERE status='stale' AND last_heartbeat < ?",
|
|
780
|
-
(thirty_min,),
|
|
781
|
-
)
|
|
782
|
-
conn.execute("DELETE FROM mesh_peers WHERE status='dead'")
|
|
783
|
-
# Delete read direct messages > 24hr old
|
|
784
|
-
conn.execute(
|
|
785
|
-
"DELETE FROM mesh_messages WHERE target_type='peer' AND read=1 "
|
|
786
|
-
"AND created_at < ?",
|
|
787
|
-
(day_ago,),
|
|
788
|
-
)
|
|
789
|
-
# v3.4.6: Delete EXPIRED messages (48h TTL for broadcast/project)
|
|
790
|
-
conn.execute(
|
|
791
|
-
"DELETE FROM mesh_messages WHERE expires_at IS NOT NULL "
|
|
792
|
-
"AND expires_at < ?",
|
|
793
|
-
(now_iso,),
|
|
794
|
-
)
|
|
795
|
-
# v3.4.6: Clean up orphaned mesh_reads entries
|
|
796
|
-
conn.execute(
|
|
797
|
-
"DELETE FROM mesh_reads WHERE message_id NOT IN "
|
|
798
|
-
"(SELECT id FROM mesh_messages)",
|
|
799
|
-
)
|
|
800
|
-
# Delete expired locks (the 9999-… sentinel sorts after any real now)
|
|
801
|
-
conn.execute(
|
|
802
|
-
"DELETE FROM mesh_locks WHERE expires_at < ?",
|
|
803
|
-
(now_iso,),
|
|
804
|
-
)
|
|
805
|
-
# v3.4.6: Delete old events (keep last 7 days)
|
|
806
|
-
conn.execute(
|
|
807
|
-
"DELETE FROM mesh_events WHERE created_at < ?",
|
|
808
|
-
(week_ago,),
|
|
809
|
-
)
|
|
810
|
-
conn.commit()
|
|
811
|
-
|
|
812
|
-
self._write_with_retry(_cleanup)
|