superlocalmemory 4.1.13 → 4.1.14
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 +48 -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,949 +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
|
|
4
|
-
|
|
5
|
-
"""Daemon-owned, cross-process coordinator for canonical SQLite writes.
|
|
6
|
-
|
|
7
|
-
This module implements the 3.8.6 first migration step: one daemon can claim
|
|
8
|
-
one ``memory.db`` path, and all in-process work submitted to that owner is
|
|
9
|
-
serialised by a single connection-owning thread. It intentionally does not
|
|
10
|
-
create an IPC protocol. CLI, MCP, and dashboard clients must reach this
|
|
11
|
-
coordinator through the authenticated daemon boundary as later migration work
|
|
12
|
-
removes their direct writes.
|
|
13
|
-
"""
|
|
14
|
-
|
|
15
|
-
from __future__ import annotations
|
|
16
|
-
|
|
17
|
-
import json
|
|
18
|
-
import os
|
|
19
|
-
import sqlite3
|
|
20
|
-
import threading
|
|
21
|
-
import time
|
|
22
|
-
import uuid
|
|
23
|
-
from collections import deque
|
|
24
|
-
from contextlib import AbstractContextManager
|
|
25
|
-
from dataclasses import dataclass, field
|
|
26
|
-
from enum import StrEnum
|
|
27
|
-
from pathlib import Path
|
|
28
|
-
from types import MappingProxyType, ModuleType
|
|
29
|
-
from typing import Any, Callable, Literal, Mapping
|
|
30
|
-
|
|
31
|
-
from superlocalmemory.core.file_lock import LockHeldError, exclusive_lock
|
|
32
|
-
from superlocalmemory.storage.write_lock import get_write_lock
|
|
33
|
-
|
|
34
|
-
try:
|
|
35
|
-
import portalocker as _portalocker_import
|
|
36
|
-
except ImportError: # pragma: no cover - pinned production dependency
|
|
37
|
-
_portalocker: ModuleType | None = None
|
|
38
|
-
else:
|
|
39
|
-
_portalocker = _portalocker_import
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
class WriteCoordinatorError(RuntimeError):
|
|
43
|
-
"""Base class for canonical writer failures."""
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
class OwnershipRequiredError(WriteCoordinatorError):
|
|
47
|
-
"""Raised when a process has not claimed canonical write ownership."""
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
class QueueOverloadedError(WriteCoordinatorError):
|
|
51
|
-
"""Raised when bounded coordinator capacity is exhausted."""
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
class WriteDeadlineExceededError(WriteCoordinatorError):
|
|
55
|
-
"""Raised before a queued write can start within its caller deadline."""
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
class CommandConflictError(WriteCoordinatorError):
|
|
59
|
-
"""A durable command id was reused for a different immutable request."""
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
class CommandRejectedError(WriteCoordinatorError):
|
|
63
|
-
"""A deterministic command cannot succeed if replayed unchanged."""
|
|
64
|
-
|
|
65
|
-
def __init__(self, error_code: str = "COMMAND_REJECTED") -> None:
|
|
66
|
-
super().__init__("canonical command was deterministically rejected")
|
|
67
|
-
self.error_code = error_code
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
class WriterStalledError(WriteCoordinatorError):
|
|
71
|
-
"""Raised by submit()/execute() when the worker thread has stalled past STALL_THRESHOLD.
|
|
72
|
-
|
|
73
|
-
This is the circuit-breaker fast-fail: instead of enqueueing behind a dead worker
|
|
74
|
-
(causing every new caller to wait for their per-item timeout), new callers get
|
|
75
|
-
this immediate, actionable error. Admin remediation: check /operations/failed,
|
|
76
|
-
then restart the daemon if the stall persists.
|
|
77
|
-
"""
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
class Lane(StrEnum):
|
|
81
|
-
"""Scheduling lanes, ordered to protect foreground memory operations."""
|
|
82
|
-
|
|
83
|
-
FOREGROUND = "foreground"
|
|
84
|
-
CONTROL = "control"
|
|
85
|
-
BACKGROUND = "background"
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
class CommandKind(StrEnum):
|
|
89
|
-
"""Durable command families accepted by the canonical writer.
|
|
90
|
-
|
|
91
|
-
A command is intentionally more specific than an SQL statement. The
|
|
92
|
-
coordinator can therefore make the receipt part of the same SQLite commit
|
|
93
|
-
and safely replay an acknowledged request without calling its handler.
|
|
94
|
-
"""
|
|
95
|
-
|
|
96
|
-
ADMISSION = "admission"
|
|
97
|
-
DELETE_FACT = "delete_fact"
|
|
98
|
-
UPDATE_FACT = "update_fact"
|
|
99
|
-
PROPOSE_CORRECTION = "propose_correction"
|
|
100
|
-
APPLY_CORRECTION = "apply_correction"
|
|
101
|
-
REJECT_CORRECTION = "reject_correction"
|
|
102
|
-
ROLLBACK_CORRECTION = "rollback_correction"
|
|
103
|
-
ARCHIVE_FACT = "archive_fact"
|
|
104
|
-
MERGE_FACT = "merge_fact"
|
|
105
|
-
SET_FACT_SCOPE = "set_fact_scope"
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
JsonValue = None | bool | int | float | str | tuple["JsonValue", ...] | Mapping[str, "JsonValue"]
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
def _freeze_json(value: Any) -> JsonValue:
|
|
112
|
-
"""Make JSON-shaped command/receipt data immutable before it crosses lanes."""
|
|
113
|
-
if value is None or isinstance(value, (bool, int, float, str)):
|
|
114
|
-
return value
|
|
115
|
-
if isinstance(value, Mapping):
|
|
116
|
-
return MappingProxyType({str(key): _freeze_json(item) for key, item in value.items()})
|
|
117
|
-
if isinstance(value, (list, tuple)):
|
|
118
|
-
return tuple(_freeze_json(item) for item in value)
|
|
119
|
-
raise TypeError("command payloads and receipts must be JSON-compatible")
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
def _thaw_json(value: JsonValue) -> Any:
|
|
123
|
-
"""Return ordinary JSON-compatible values for stable receipt encoding."""
|
|
124
|
-
if isinstance(value, Mapping):
|
|
125
|
-
return {key: _thaw_json(item) for key, item in value.items()}
|
|
126
|
-
if isinstance(value, tuple):
|
|
127
|
-
return [_thaw_json(item) for item in value]
|
|
128
|
-
return value
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
@dataclass(frozen=True, slots=True)
|
|
132
|
-
class WriteCommand:
|
|
133
|
-
"""An immutable request that can be committed and replayed exactly once."""
|
|
134
|
-
|
|
135
|
-
command_id: str
|
|
136
|
-
kind: CommandKind
|
|
137
|
-
payload: Mapping[str, JsonValue] = field(default_factory=lambda: MappingProxyType({}))
|
|
138
|
-
|
|
139
|
-
def __post_init__(self) -> None:
|
|
140
|
-
if not self.command_id or not isinstance(self.command_id, str):
|
|
141
|
-
raise ValueError("command_id must be a non-empty string")
|
|
142
|
-
object.__setattr__(self, "kind", CommandKind(self.kind))
|
|
143
|
-
frozen = _freeze_json(dict(self.payload))
|
|
144
|
-
if not isinstance(frozen, Mapping): # pragma: no cover - dict input is enforced above
|
|
145
|
-
raise TypeError("command payload must be an object")
|
|
146
|
-
object.__setattr__(self, "payload", frozen)
|
|
147
|
-
|
|
148
|
-
@classmethod
|
|
149
|
-
def create(
|
|
150
|
-
cls,
|
|
151
|
-
kind: CommandKind,
|
|
152
|
-
payload: Mapping[str, Any] | None = None,
|
|
153
|
-
*,
|
|
154
|
-
command_id: str | None = None,
|
|
155
|
-
) -> "WriteCommand":
|
|
156
|
-
"""Create a command with a caller-supplied or generated idempotency key."""
|
|
157
|
-
resolved_id = str(uuid.uuid4()) if command_id is None else command_id
|
|
158
|
-
return cls(resolved_id, CommandKind(kind), payload or {})
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
@dataclass(frozen=True, slots=True)
|
|
162
|
-
class WriteResult:
|
|
163
|
-
"""An immutable receipt produced by a typed command handler."""
|
|
164
|
-
|
|
165
|
-
command_id: str
|
|
166
|
-
kind: CommandKind
|
|
167
|
-
receipt: Mapping[str, JsonValue] = field(default_factory=lambda: MappingProxyType({}))
|
|
168
|
-
|
|
169
|
-
def __post_init__(self) -> None:
|
|
170
|
-
if not self.command_id or not isinstance(self.command_id, str):
|
|
171
|
-
raise ValueError("command_id must be a non-empty string")
|
|
172
|
-
object.__setattr__(self, "kind", CommandKind(self.kind))
|
|
173
|
-
frozen = _freeze_json(dict(self.receipt))
|
|
174
|
-
if not isinstance(frozen, Mapping): # pragma: no cover - dict input is enforced above
|
|
175
|
-
raise TypeError("command receipt must be an object")
|
|
176
|
-
object.__setattr__(self, "receipt", frozen)
|
|
177
|
-
|
|
178
|
-
@classmethod
|
|
179
|
-
def from_receipt(
|
|
180
|
-
cls,
|
|
181
|
-
command: WriteCommand,
|
|
182
|
-
receipt: Mapping[str, Any] | None = None,
|
|
183
|
-
) -> "WriteResult":
|
|
184
|
-
"""Bind a handler receipt to the command being processed."""
|
|
185
|
-
return cls(command.command_id, command.kind, receipt or {})
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
@dataclass(frozen=True, slots=True)
|
|
189
|
-
class WriteCapability:
|
|
190
|
-
"""A worker-thread-only capability for DatabaseManager binding.
|
|
191
|
-
|
|
192
|
-
This is an in-process authority boundary, not a security credential. Its
|
|
193
|
-
coordinator identity token, exact resolved database path, and worker
|
|
194
|
-
identity keep ordinary manager code from attaching a foreign connection.
|
|
195
|
-
"""
|
|
196
|
-
|
|
197
|
-
db_path: Path
|
|
198
|
-
owner_id: str
|
|
199
|
-
worker_ident: int
|
|
200
|
-
_issuer: Any = field(repr=False, compare=False)
|
|
201
|
-
_token: object = field(repr=False, compare=False)
|
|
202
|
-
|
|
203
|
-
def _validate(self, db_path: Path) -> None:
|
|
204
|
-
issuer = self._issuer
|
|
205
|
-
if getattr(issuer, "_capability_token", None) is not self._token:
|
|
206
|
-
raise WriteCoordinatorError("untrusted coordinator capability")
|
|
207
|
-
if getattr(issuer, "owner_id", None) != self.owner_id:
|
|
208
|
-
raise WriteCoordinatorError("coordinator capability identity mismatch")
|
|
209
|
-
if getattr(issuer, "db_path", None) != self.db_path:
|
|
210
|
-
raise WriteCoordinatorError("coordinator capability database identity mismatch")
|
|
211
|
-
if getattr(issuer, "_worker_ident", None) != self.worker_ident:
|
|
212
|
-
raise WriteCoordinatorError("coordinator capability worker identity mismatch")
|
|
213
|
-
if threading.get_ident() != self.worker_ident:
|
|
214
|
-
raise WriteCoordinatorError("coordinator capability used outside its worker thread")
|
|
215
|
-
if db_path.expanduser().resolve() != self.db_path:
|
|
216
|
-
raise WriteCoordinatorError("coordinator capability targets a different database")
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
CommandHandler = Callable[[sqlite3.Connection, WriteCapability, WriteCommand], WriteResult]
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
_Priority = Literal["foreground", "control", "background"]
|
|
223
|
-
_MAX_QUEUE_DEPTH = 4_096
|
|
224
|
-
_FOREGROUND_BURST = 8
|
|
225
|
-
_SQLITE_BUSY_CODES = {sqlite3.SQLITE_BUSY, sqlite3.SQLITE_LOCKED}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
@dataclass(slots=True)
|
|
229
|
-
class _Execution:
|
|
230
|
-
sql: str | None
|
|
231
|
-
parameters: tuple[Any, ...]
|
|
232
|
-
lane: Lane
|
|
233
|
-
deadline: float
|
|
234
|
-
command: WriteCommand | None = None
|
|
235
|
-
completion: threading.Event = field(default_factory=threading.Event)
|
|
236
|
-
rows: list[sqlite3.Row] | None = None
|
|
237
|
-
result: WriteResult | None = None
|
|
238
|
-
error: BaseException | None = None
|
|
239
|
-
cancelled: bool = False
|
|
240
|
-
commit_started: bool = False
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
class WriteCoordinator:
|
|
244
|
-
"""Own one writable ``memory.db`` connection for a daemon lifetime.
|
|
245
|
-
|
|
246
|
-
``claim_ownership`` is deliberately separate from construction so a caller
|
|
247
|
-
can report an already-running daemon without attempting an SQLite open.
|
|
248
|
-
``execute`` exists only as the migration adapter for small bounded storage
|
|
249
|
-
commands. Product code will use typed commands as writer families move to
|
|
250
|
-
this coordinator.
|
|
251
|
-
"""
|
|
252
|
-
|
|
253
|
-
#: Default stall threshold in seconds. Well above the per-item 1s deadline.
|
|
254
|
-
#: The watchdog is inert for any item that completes within this window,
|
|
255
|
-
#: preserving healthy-path performance byte-for-byte.
|
|
256
|
-
_DEFAULT_STALL_THRESHOLD_S: float = 30.0
|
|
257
|
-
|
|
258
|
-
def __init__(
|
|
259
|
-
self,
|
|
260
|
-
db_path: str | Path,
|
|
261
|
-
*,
|
|
262
|
-
owner_id: str | None = None,
|
|
263
|
-
max_queue_depth: int = _MAX_QUEUE_DEPTH,
|
|
264
|
-
stall_threshold: float | None = None,
|
|
265
|
-
) -> None:
|
|
266
|
-
if max_queue_depth < 1:
|
|
267
|
-
raise ValueError("max_queue_depth must be at least one")
|
|
268
|
-
self._db_path = Path(db_path).expanduser().resolve()
|
|
269
|
-
self._owner_id = owner_id or str(uuid.uuid4())
|
|
270
|
-
self._lock_path = self._db_path.with_name(f"{self._db_path.name}.writer.lock")
|
|
271
|
-
# Expand-migrate-contract compatibility: legacy in-process writers
|
|
272
|
-
# already serialize on this per-path RLock. The coordinator must join
|
|
273
|
-
# that same critical section until every background/control writer has
|
|
274
|
-
# moved behind typed commands; otherwise the new admission connection
|
|
275
|
-
# can race the materializer connection and reintroduce SQLITE_BUSY.
|
|
276
|
-
self._process_write_lock = get_write_lock(self._db_path)
|
|
277
|
-
self._max_queue_depth = max_queue_depth
|
|
278
|
-
self._ownership_context: AbstractContextManager[int] | None = None
|
|
279
|
-
self._lock_fd: int | None = None
|
|
280
|
-
self._condition = threading.Condition()
|
|
281
|
-
self._queues: dict[Lane, deque[_Execution]] = {
|
|
282
|
-
Lane.FOREGROUND: deque(),
|
|
283
|
-
Lane.CONTROL: deque(),
|
|
284
|
-
Lane.BACKGROUND: deque(),
|
|
285
|
-
}
|
|
286
|
-
self._queued_count = 0
|
|
287
|
-
self._foreground_served = 0
|
|
288
|
-
self._stopping = False
|
|
289
|
-
self._worker: threading.Thread | None = None
|
|
290
|
-
self._lease_release_pending = False
|
|
291
|
-
self._lease_release_reaper: threading.Thread | None = None
|
|
292
|
-
self._worker_ready = threading.Event()
|
|
293
|
-
self._worker_error: BaseException | None = None
|
|
294
|
-
self._worker_ident: int | None = None
|
|
295
|
-
self._capability_token = object()
|
|
296
|
-
self._handlers: dict[CommandKind, CommandHandler] = {}
|
|
297
|
-
# ---- Stall watchdog state (additive — inert on the healthy path) ----
|
|
298
|
-
# Protected by self._condition for thread-safety.
|
|
299
|
-
self._stall_threshold: float = (
|
|
300
|
-
stall_threshold if stall_threshold is not None else self._DEFAULT_STALL_THRESHOLD_S
|
|
301
|
-
)
|
|
302
|
-
self._inflight_started_at: float | None = None
|
|
303
|
-
self._inflight_op_id: str | None = None
|
|
304
|
-
self._writer_stalled: bool = False
|
|
305
|
-
self._stall_logged_once: bool = False
|
|
306
|
-
|
|
307
|
-
@property
|
|
308
|
-
def db_path(self) -> Path:
|
|
309
|
-
"""Resolved canonical database path owned by this coordinator."""
|
|
310
|
-
return self._db_path
|
|
311
|
-
|
|
312
|
-
@property
|
|
313
|
-
def owner_id(self) -> str:
|
|
314
|
-
"""Opaque daemon instance identifier recorded in the ownership lease."""
|
|
315
|
-
return self._owner_id
|
|
316
|
-
|
|
317
|
-
@property
|
|
318
|
-
def stall_threshold(self) -> float:
|
|
319
|
-
"""Seconds before an in-flight item is considered stalled."""
|
|
320
|
-
return self._stall_threshold
|
|
321
|
-
|
|
322
|
-
@property
|
|
323
|
-
def writer_stalled(self) -> bool:
|
|
324
|
-
"""True when the worker thread has exceeded stall_threshold on one item.
|
|
325
|
-
|
|
326
|
-
This is a dynamic check — computed from the current inflight age without
|
|
327
|
-
requiring a submitter to trigger _check_stall(). Safe to poll from any thread.
|
|
328
|
-
"""
|
|
329
|
-
with self._condition:
|
|
330
|
-
if self._writer_stalled:
|
|
331
|
-
return True
|
|
332
|
-
started = self._inflight_started_at
|
|
333
|
-
if started is None:
|
|
334
|
-
return False
|
|
335
|
-
return (time.monotonic() - started) > self._stall_threshold
|
|
336
|
-
|
|
337
|
-
def inflight_info(self) -> dict:
|
|
338
|
-
"""Return stall health snapshot for /health and get_status.
|
|
339
|
-
|
|
340
|
-
Returns:
|
|
341
|
-
{"stalled": bool, "op_id": str | None, "age_s": float | None}
|
|
342
|
-
|
|
343
|
-
Pure read — never raises.
|
|
344
|
-
"""
|
|
345
|
-
with self._condition:
|
|
346
|
-
started = self._inflight_started_at
|
|
347
|
-
stalled = self._writer_stalled
|
|
348
|
-
op_id = self._inflight_op_id
|
|
349
|
-
if started is None:
|
|
350
|
-
return {"stalled": False, "op_id": None, "age_s": None}
|
|
351
|
-
now = time.monotonic()
|
|
352
|
-
age = now - started
|
|
353
|
-
# Dynamic stall check (same logic as writer_stalled property)
|
|
354
|
-
is_stalled = stalled or (age > self._stall_threshold)
|
|
355
|
-
return {"stalled": is_stalled, "op_id": op_id, "age_s": round(age, 2)}
|
|
356
|
-
|
|
357
|
-
def claim_ownership(self) -> bool:
|
|
358
|
-
"""Claim the cross-platform owner lease without waiting on another daemon."""
|
|
359
|
-
if _portalocker is None:
|
|
360
|
-
raise WriteCoordinatorError("portalocker is required for canonical writer ownership")
|
|
361
|
-
if self._ownership_context is not None:
|
|
362
|
-
if self._lease_release_pending:
|
|
363
|
-
raise WriteCoordinatorError("canonical writer is still shutting down")
|
|
364
|
-
return True
|
|
365
|
-
self._lock_path.parent.mkdir(parents=True, exist_ok=True)
|
|
366
|
-
context = exclusive_lock(self._lock_path, timeout_s=0.0)
|
|
367
|
-
try:
|
|
368
|
-
fd = context.__enter__()
|
|
369
|
-
except LockHeldError:
|
|
370
|
-
return False
|
|
371
|
-
except OSError as exc:
|
|
372
|
-
raise WriteCoordinatorError(
|
|
373
|
-
f"could not claim canonical writer lock for {self._db_path}"
|
|
374
|
-
) from exc
|
|
375
|
-
|
|
376
|
-
self._ownership_context = context
|
|
377
|
-
self._lock_fd = fd
|
|
378
|
-
try:
|
|
379
|
-
self._write_owner_metadata(fd)
|
|
380
|
-
except BaseException:
|
|
381
|
-
self.release_ownership()
|
|
382
|
-
raise
|
|
383
|
-
return True
|
|
384
|
-
|
|
385
|
-
def release_ownership(self) -> None:
|
|
386
|
-
"""Stop the worker and release the ownership lease, if held."""
|
|
387
|
-
try:
|
|
388
|
-
self.stop()
|
|
389
|
-
except WriteCoordinatorError:
|
|
390
|
-
self._release_lease_after_worker_exit()
|
|
391
|
-
raise
|
|
392
|
-
self._release_lease_if_worker_stopped()
|
|
393
|
-
|
|
394
|
-
def start(self) -> None:
|
|
395
|
-
"""Start the sole connection-owning worker after a successful claim."""
|
|
396
|
-
self._require_ownership()
|
|
397
|
-
with self._condition:
|
|
398
|
-
worker = self._worker
|
|
399
|
-
if worker is None:
|
|
400
|
-
if self._lease_release_pending:
|
|
401
|
-
raise WriteCoordinatorError("canonical writer is still shutting down")
|
|
402
|
-
self._stopping = False
|
|
403
|
-
self._worker_error = None
|
|
404
|
-
self._worker_ready.clear()
|
|
405
|
-
worker = threading.Thread(
|
|
406
|
-
target=self._run,
|
|
407
|
-
name=f"slm-write-coordinator-{self._owner_id[:8]}",
|
|
408
|
-
daemon=True,
|
|
409
|
-
)
|
|
410
|
-
self._worker = worker
|
|
411
|
-
worker.start()
|
|
412
|
-
else:
|
|
413
|
-
if self._stopping:
|
|
414
|
-
raise WriteCoordinatorError("canonical writer is stopping")
|
|
415
|
-
if not self._worker_ready.wait(timeout=2.0):
|
|
416
|
-
self.stop()
|
|
417
|
-
raise WriteCoordinatorError("canonical writer did not start within two seconds")
|
|
418
|
-
if self._worker_error is not None:
|
|
419
|
-
error = self._worker_error
|
|
420
|
-
self.stop()
|
|
421
|
-
raise WriteCoordinatorError("canonical writer could not open memory.db") from error
|
|
422
|
-
|
|
423
|
-
def stop(self, deadline_s: float = 2.0) -> None:
|
|
424
|
-
"""Stop accepting work and wait briefly for the sole writer thread."""
|
|
425
|
-
worker = self._worker
|
|
426
|
-
if worker is None:
|
|
427
|
-
return
|
|
428
|
-
with self._condition:
|
|
429
|
-
self._stopping = True
|
|
430
|
-
self._condition.notify_all()
|
|
431
|
-
worker.join(timeout=max(0.0, deadline_s))
|
|
432
|
-
if worker.is_alive():
|
|
433
|
-
raise WriteCoordinatorError("canonical writer did not stop before its deadline")
|
|
434
|
-
self._worker = None
|
|
435
|
-
|
|
436
|
-
def _release_lease_after_worker_exit(self) -> None:
|
|
437
|
-
"""Release a requested lease only after its live worker has terminated."""
|
|
438
|
-
with self._condition:
|
|
439
|
-
worker = self._worker
|
|
440
|
-
if worker is None or not worker.is_alive() or self._lease_release_pending:
|
|
441
|
-
return
|
|
442
|
-
self._lease_release_pending = True
|
|
443
|
-
reaper = threading.Thread(
|
|
444
|
-
target=self._reap_worker_then_release_lease,
|
|
445
|
-
args=(worker,),
|
|
446
|
-
name=f"slm-write-lease-reaper-{self._owner_id[:8]}",
|
|
447
|
-
daemon=True,
|
|
448
|
-
)
|
|
449
|
-
self._lease_release_reaper = reaper
|
|
450
|
-
reaper.start()
|
|
451
|
-
|
|
452
|
-
def _reap_worker_then_release_lease(self, worker: threading.Thread) -> None:
|
|
453
|
-
"""Wait without a shutdown deadline, then relinquish a drained lease."""
|
|
454
|
-
worker.join()
|
|
455
|
-
self._release_lease_if_worker_stopped(expected_worker=worker)
|
|
456
|
-
|
|
457
|
-
def _release_lease_if_worker_stopped(
|
|
458
|
-
self,
|
|
459
|
-
*,
|
|
460
|
-
expected_worker: threading.Thread | None = None,
|
|
461
|
-
) -> None:
|
|
462
|
-
"""Release ownership only when no writer thread can still use the DB."""
|
|
463
|
-
with self._condition:
|
|
464
|
-
worker = self._worker
|
|
465
|
-
if worker is not None:
|
|
466
|
-
if expected_worker is not None and worker is not expected_worker:
|
|
467
|
-
return
|
|
468
|
-
if worker.is_alive():
|
|
469
|
-
return
|
|
470
|
-
self._worker = None
|
|
471
|
-
context = self._ownership_context
|
|
472
|
-
self._ownership_context = None
|
|
473
|
-
self._lock_fd = None
|
|
474
|
-
self._lease_release_pending = False
|
|
475
|
-
self._lease_release_reaper = None
|
|
476
|
-
if context is not None:
|
|
477
|
-
context.__exit__(None, None, None)
|
|
478
|
-
|
|
479
|
-
def _check_stall(self) -> None:
|
|
480
|
-
"""Circuit-breaker check: raise WriterStalledError if worker is stalled.
|
|
481
|
-
|
|
482
|
-
Called from submit() and execute() BEFORE enqueueing. Inert on the
|
|
483
|
-
healthy path (started_at is None or within stall_threshold).
|
|
484
|
-
Thread-safe: reads only condition-protected state.
|
|
485
|
-
"""
|
|
486
|
-
with self._condition:
|
|
487
|
-
started = self._inflight_started_at
|
|
488
|
-
if started is None:
|
|
489
|
-
return
|
|
490
|
-
age = time.monotonic() - started
|
|
491
|
-
if age > self._stall_threshold:
|
|
492
|
-
if not self._stall_logged_once:
|
|
493
|
-
import logging as _log
|
|
494
|
-
_log.getLogger(__name__).critical(
|
|
495
|
-
"WriteCoordinator: worker thread stalled for %.1fs on op=%s; "
|
|
496
|
-
"new submitters will receive WriterStalledError",
|
|
497
|
-
age,
|
|
498
|
-
self._inflight_op_id,
|
|
499
|
-
)
|
|
500
|
-
self._stall_logged_once = True
|
|
501
|
-
self._writer_stalled = True
|
|
502
|
-
if self._writer_stalled:
|
|
503
|
-
raise WriterStalledError(
|
|
504
|
-
"write subsystem stalled; admin remediation required"
|
|
505
|
-
)
|
|
506
|
-
|
|
507
|
-
def execute(
|
|
508
|
-
self,
|
|
509
|
-
sql: str,
|
|
510
|
-
parameters: tuple[Any, ...] = (),
|
|
511
|
-
*,
|
|
512
|
-
priority: _Priority | Lane = Lane.FOREGROUND,
|
|
513
|
-
timeout: float = 1.0,
|
|
514
|
-
) -> list[sqlite3.Row]:
|
|
515
|
-
"""Execute one bounded statement through the daemon-owned connection.
|
|
516
|
-
|
|
517
|
-
The migration adapter rejects empty SQL and expired requests. It is
|
|
518
|
-
intentionally not an escape hatch for slow work or unbounded batches.
|
|
519
|
-
"""
|
|
520
|
-
if not isinstance(sql, str) or not sql.strip():
|
|
521
|
-
raise ValueError("sql must be a non-empty statement")
|
|
522
|
-
if timeout <= 0:
|
|
523
|
-
raise ValueError("timeout must be greater than zero")
|
|
524
|
-
self._check_stall() # circuit-breaker (inert if no stall)
|
|
525
|
-
lane = self._coerce_lane(priority)
|
|
526
|
-
self.start()
|
|
527
|
-
item = _Execution(
|
|
528
|
-
sql=sql,
|
|
529
|
-
parameters=tuple(parameters),
|
|
530
|
-
lane=lane,
|
|
531
|
-
deadline=time.monotonic() + timeout,
|
|
532
|
-
)
|
|
533
|
-
self._enqueue(item)
|
|
534
|
-
self._wait_for_completion(item)
|
|
535
|
-
if item.error is not None:
|
|
536
|
-
raise item.error
|
|
537
|
-
return item.rows or []
|
|
538
|
-
|
|
539
|
-
def register_handler(self, kind: CommandKind, handler: CommandHandler) -> None:
|
|
540
|
-
"""Register the sole handler for a durable command family.
|
|
541
|
-
|
|
542
|
-
Handler registration is a daemon-start concern. Refusing changes
|
|
543
|
-
after the worker starts avoids a request observing a partially changed
|
|
544
|
-
command dispatch table.
|
|
545
|
-
"""
|
|
546
|
-
if not callable(handler):
|
|
547
|
-
raise TypeError("command handler must be callable")
|
|
548
|
-
command_kind = CommandKind(kind)
|
|
549
|
-
with self._condition:
|
|
550
|
-
if self._worker is not None:
|
|
551
|
-
raise WriteCoordinatorError("command handlers must be registered before start")
|
|
552
|
-
if command_kind in self._handlers:
|
|
553
|
-
raise WriteCoordinatorError(f"handler already registered for {command_kind.value}")
|
|
554
|
-
self._handlers[command_kind] = handler
|
|
555
|
-
|
|
556
|
-
def submit(
|
|
557
|
-
self,
|
|
558
|
-
command: WriteCommand,
|
|
559
|
-
*,
|
|
560
|
-
priority: _Priority | Lane = Lane.FOREGROUND,
|
|
561
|
-
timeout: float = 1.0,
|
|
562
|
-
) -> WriteResult:
|
|
563
|
-
"""Run a typed command and persist its receipt in the same commit."""
|
|
564
|
-
if not isinstance(command, WriteCommand):
|
|
565
|
-
raise TypeError("command must be a WriteCommand")
|
|
566
|
-
if timeout <= 0:
|
|
567
|
-
raise ValueError("timeout must be greater than zero")
|
|
568
|
-
self._check_stall() # circuit-breaker (inert if no stall)
|
|
569
|
-
lane = self._coerce_lane(priority)
|
|
570
|
-
self.start()
|
|
571
|
-
item = _Execution(
|
|
572
|
-
sql=None,
|
|
573
|
-
parameters=(),
|
|
574
|
-
lane=lane,
|
|
575
|
-
deadline=time.monotonic() + timeout,
|
|
576
|
-
command=command,
|
|
577
|
-
)
|
|
578
|
-
self._enqueue(item)
|
|
579
|
-
self._wait_for_completion(item)
|
|
580
|
-
if item.error is not None:
|
|
581
|
-
raise item.error
|
|
582
|
-
if item.result is None: # pragma: no cover - defensive worker invariant
|
|
583
|
-
raise WriteCoordinatorError("canonical command completed without a receipt")
|
|
584
|
-
return item.result
|
|
585
|
-
|
|
586
|
-
def _wait_for_completion(self, item: _Execution) -> None:
|
|
587
|
-
"""Cancel before commit or wait through an already-linearized commit."""
|
|
588
|
-
remaining = max(0.0, item.deadline - time.monotonic())
|
|
589
|
-
if item.completion.wait(remaining):
|
|
590
|
-
return
|
|
591
|
-
with self._condition:
|
|
592
|
-
if item.completion.is_set():
|
|
593
|
-
return
|
|
594
|
-
if not item.commit_started:
|
|
595
|
-
item.cancelled = True
|
|
596
|
-
raise WriteDeadlineExceededError(
|
|
597
|
-
"canonical write exceeded its caller deadline"
|
|
598
|
-
)
|
|
599
|
-
# Commit is the linearization point. Once it starts, return its real
|
|
600
|
-
# result instead of reporting an ambiguous timeout followed by a
|
|
601
|
-
# durable mutation.
|
|
602
|
-
item.completion.wait()
|
|
603
|
-
|
|
604
|
-
def _enqueue(self, item: _Execution) -> None:
|
|
605
|
-
with self._condition:
|
|
606
|
-
if self._stopping:
|
|
607
|
-
raise WriteCoordinatorError("canonical writer is stopping")
|
|
608
|
-
if self._queued_count >= self._max_queue_depth:
|
|
609
|
-
raise QueueOverloadedError("canonical writer queue is full")
|
|
610
|
-
self._queues[item.lane].append(item)
|
|
611
|
-
self._queued_count += 1
|
|
612
|
-
self._condition.notify()
|
|
613
|
-
|
|
614
|
-
def _run(self) -> None:
|
|
615
|
-
self._worker_ident = threading.get_ident()
|
|
616
|
-
try:
|
|
617
|
-
conn = self._open_connection()
|
|
618
|
-
except BaseException as exc:
|
|
619
|
-
self._worker_error = exc
|
|
620
|
-
self._worker_ready.set()
|
|
621
|
-
return
|
|
622
|
-
self._worker_ready.set()
|
|
623
|
-
try:
|
|
624
|
-
while True:
|
|
625
|
-
item = self._next_item()
|
|
626
|
-
if item is None:
|
|
627
|
-
return
|
|
628
|
-
self._execute_item(conn, item)
|
|
629
|
-
finally:
|
|
630
|
-
self._worker_ident = None
|
|
631
|
-
conn.close()
|
|
632
|
-
|
|
633
|
-
def _open_connection(self) -> sqlite3.Connection:
|
|
634
|
-
"""The coordinator's own connection, on the same waiting policy as the rest.
|
|
635
|
-
|
|
636
|
-
It waited one second while every other writer waited ten. A caller
|
|
637
|
-
legitimately holding the write lock for longer than a second -- a large
|
|
638
|
-
batch, a store during maintenance -- made the coordinator's next write
|
|
639
|
-
raise "database is locked" and lose the item, while an ordinary write
|
|
640
|
-
issued at the same moment simply waited and succeeded.
|
|
641
|
-
|
|
642
|
-
Reproduced: a 3,010 ms hold blocked the coordinator for 1,004 ms and then
|
|
643
|
-
dropped its write. There was no reason for the two to disagree, and the
|
|
644
|
-
shorter one belonged to the path whose failure loses data rather than
|
|
645
|
-
retries.
|
|
646
|
-
"""
|
|
647
|
-
from superlocalmemory.storage.database import _BUSY_TIMEOUT_MS
|
|
648
|
-
|
|
649
|
-
conn = sqlite3.connect(
|
|
650
|
-
str(self._db_path), timeout=_BUSY_TIMEOUT_MS / 1000.0,
|
|
651
|
-
)
|
|
652
|
-
conn.row_factory = sqlite3.Row
|
|
653
|
-
conn.execute("PRAGMA foreign_keys=ON")
|
|
654
|
-
conn.execute(f"PRAGMA busy_timeout={_BUSY_TIMEOUT_MS}")
|
|
655
|
-
conn.execute("PRAGMA journal_mode=WAL")
|
|
656
|
-
return conn
|
|
657
|
-
|
|
658
|
-
def _next_item(self) -> _Execution | None:
|
|
659
|
-
with self._condition:
|
|
660
|
-
while self._queued_count == 0 and not self._stopping:
|
|
661
|
-
self._condition.wait()
|
|
662
|
-
if self._queued_count == 0:
|
|
663
|
-
return None
|
|
664
|
-
|
|
665
|
-
lane = self._select_lane()
|
|
666
|
-
item = self._queues[lane].popleft()
|
|
667
|
-
self._queued_count -= 1
|
|
668
|
-
return item
|
|
669
|
-
|
|
670
|
-
def _select_lane(self) -> Lane:
|
|
671
|
-
foreground = self._queues[Lane.FOREGROUND]
|
|
672
|
-
control = self._queues[Lane.CONTROL]
|
|
673
|
-
background = self._queues[Lane.BACKGROUND]
|
|
674
|
-
can_continue_foreground = self._foreground_served < _FOREGROUND_BURST
|
|
675
|
-
if foreground and (can_continue_foreground or not (control or background)):
|
|
676
|
-
self._foreground_served += 1
|
|
677
|
-
return Lane.FOREGROUND
|
|
678
|
-
if control:
|
|
679
|
-
self._foreground_served = 0
|
|
680
|
-
return Lane.CONTROL
|
|
681
|
-
if background:
|
|
682
|
-
self._foreground_served = 0
|
|
683
|
-
return Lane.BACKGROUND
|
|
684
|
-
self._foreground_served = 0
|
|
685
|
-
return Lane.FOREGROUND
|
|
686
|
-
|
|
687
|
-
def _execute_item(self, conn: sqlite3.Connection, item: _Execution) -> None:
|
|
688
|
-
# ------------------------------------------------------------------
|
|
689
|
-
# Stall watchdog: record inflight state so _check_stall() can detect
|
|
690
|
-
# a frozen worker. Cleared in the finally block. This guard is
|
|
691
|
-
# strictly ADDITIVE — the item execution path is unchanged.
|
|
692
|
-
# ------------------------------------------------------------------
|
|
693
|
-
op_id: str | None = None
|
|
694
|
-
if item.command is not None:
|
|
695
|
-
op_id = item.command.command_id
|
|
696
|
-
with self._condition:
|
|
697
|
-
self._inflight_started_at = time.monotonic()
|
|
698
|
-
self._inflight_op_id = op_id
|
|
699
|
-
|
|
700
|
-
if item.cancelled or time.monotonic() >= item.deadline:
|
|
701
|
-
item.error = WriteDeadlineExceededError("canonical write expired before execution")
|
|
702
|
-
with self._condition:
|
|
703
|
-
self._inflight_started_at = None
|
|
704
|
-
self._inflight_op_id = None
|
|
705
|
-
self._writer_stalled = False
|
|
706
|
-
self._stall_logged_once = False
|
|
707
|
-
item.completion.set()
|
|
708
|
-
return
|
|
709
|
-
remaining = max(0.0, item.deadline - time.monotonic())
|
|
710
|
-
if not self._process_write_lock.acquire(timeout=remaining):
|
|
711
|
-
item.error = WriteDeadlineExceededError(
|
|
712
|
-
"canonical write expired waiting for its process lock"
|
|
713
|
-
)
|
|
714
|
-
with self._condition:
|
|
715
|
-
self._inflight_started_at = None
|
|
716
|
-
self._inflight_op_id = None
|
|
717
|
-
self._writer_stalled = False
|
|
718
|
-
self._stall_logged_once = False
|
|
719
|
-
item.completion.set()
|
|
720
|
-
return
|
|
721
|
-
try:
|
|
722
|
-
remaining = item.deadline - time.monotonic()
|
|
723
|
-
if remaining <= 0:
|
|
724
|
-
item.error = WriteDeadlineExceededError(
|
|
725
|
-
"canonical write expired after waiting for its process lock"
|
|
726
|
-
)
|
|
727
|
-
return
|
|
728
|
-
remaining_ms = max(
|
|
729
|
-
1,
|
|
730
|
-
int(remaining * 1_000),
|
|
731
|
-
)
|
|
732
|
-
conn.execute(f"PRAGMA busy_timeout={remaining_ms}")
|
|
733
|
-
synchronous = "FULL" if item.lane is Lane.FOREGROUND else "NORMAL"
|
|
734
|
-
conn.execute(f"PRAGMA synchronous={synchronous}")
|
|
735
|
-
conn.execute("BEGIN IMMEDIATE")
|
|
736
|
-
with self._condition:
|
|
737
|
-
if item.cancelled or time.monotonic() >= item.deadline:
|
|
738
|
-
item.error = WriteDeadlineExceededError(
|
|
739
|
-
"canonical write expired before its mutation"
|
|
740
|
-
)
|
|
741
|
-
if item.error is not None:
|
|
742
|
-
conn.rollback()
|
|
743
|
-
return
|
|
744
|
-
if item.command is not None:
|
|
745
|
-
item.result = self._execute_command(conn, item.command)
|
|
746
|
-
else:
|
|
747
|
-
if item.sql is None: # pragma: no cover - execution invariant
|
|
748
|
-
raise WriteCoordinatorError("missing coordinator SQL command")
|
|
749
|
-
cursor = conn.execute(item.sql, item.parameters)
|
|
750
|
-
item.rows = cursor.fetchall()
|
|
751
|
-
with self._condition:
|
|
752
|
-
if item.cancelled or time.monotonic() >= item.deadline:
|
|
753
|
-
item.error = WriteDeadlineExceededError(
|
|
754
|
-
"canonical write expired during its mutation"
|
|
755
|
-
)
|
|
756
|
-
else:
|
|
757
|
-
item.commit_started = True
|
|
758
|
-
if item.error is not None:
|
|
759
|
-
conn.rollback()
|
|
760
|
-
return
|
|
761
|
-
conn.commit()
|
|
762
|
-
except WriteCoordinatorError as exc:
|
|
763
|
-
conn.rollback()
|
|
764
|
-
item.error = exc
|
|
765
|
-
except sqlite3.Error as exc:
|
|
766
|
-
conn.rollback()
|
|
767
|
-
if self._is_busy(exc):
|
|
768
|
-
item.error = QueueOverloadedError("canonical writer is temporarily busy")
|
|
769
|
-
else:
|
|
770
|
-
item.error = WriteCoordinatorError("canonical write command was rejected")
|
|
771
|
-
item.error.__cause__ = exc
|
|
772
|
-
except BaseException as exc:
|
|
773
|
-
conn.rollback()
|
|
774
|
-
item.error = WriteCoordinatorError("canonical write command failed")
|
|
775
|
-
item.error.__cause__ = exc
|
|
776
|
-
finally:
|
|
777
|
-
self._process_write_lock.release()
|
|
778
|
-
# Clear inflight state and reset stall breaker so next caller can proceed.
|
|
779
|
-
with self._condition:
|
|
780
|
-
self._inflight_started_at = None
|
|
781
|
-
self._inflight_op_id = None
|
|
782
|
-
if self._writer_stalled:
|
|
783
|
-
self._writer_stalled = False
|
|
784
|
-
self._stall_logged_once = False
|
|
785
|
-
item.completion.set()
|
|
786
|
-
|
|
787
|
-
def _execute_command(self, conn: sqlite3.Connection, command: WriteCommand) -> WriteResult:
|
|
788
|
-
"""Dispatch one command and atomically append its immutable receipt."""
|
|
789
|
-
payload = _thaw_json(command.payload)
|
|
790
|
-
if not isinstance(payload, dict):
|
|
791
|
-
raise WriteCoordinatorError("command payload must be an object")
|
|
792
|
-
request_hash = _required_text(payload, "request_hash")
|
|
793
|
-
profile_id = _required_text(payload, "profile_id")
|
|
794
|
-
idempotency_key = _required_text(payload, "idempotency_key")
|
|
795
|
-
existing = conn.execute(
|
|
796
|
-
"SELECT command_kind, request_hash, profile_id, idempotency_key, "
|
|
797
|
-
"receipt_json FROM write_commits WHERE command_id = ?",
|
|
798
|
-
(command.command_id,),
|
|
799
|
-
).fetchone()
|
|
800
|
-
if existing is not None:
|
|
801
|
-
if existing["command_kind"] != command.kind.value:
|
|
802
|
-
raise CommandConflictError("command id was already committed with a different kind")
|
|
803
|
-
if (
|
|
804
|
-
existing["request_hash"] != request_hash
|
|
805
|
-
or existing["profile_id"] != profile_id
|
|
806
|
-
or existing["idempotency_key"] != idempotency_key
|
|
807
|
-
):
|
|
808
|
-
raise CommandConflictError(
|
|
809
|
-
"command id was already committed for a different request"
|
|
810
|
-
)
|
|
811
|
-
try:
|
|
812
|
-
receipt = json.loads(existing["receipt_json"])
|
|
813
|
-
except (TypeError, json.JSONDecodeError) as exc:
|
|
814
|
-
raise WriteCoordinatorError("stored command receipt is invalid") from exc
|
|
815
|
-
if not isinstance(receipt, dict):
|
|
816
|
-
raise WriteCoordinatorError("stored command receipt is not an object")
|
|
817
|
-
return WriteResult(command.command_id, command.kind, receipt)
|
|
818
|
-
|
|
819
|
-
handler = self._handlers.get(command.kind)
|
|
820
|
-
if handler is None:
|
|
821
|
-
raise WriteCoordinatorError(f"no handler registered for {command.kind.value}")
|
|
822
|
-
worker_ident = self._worker_ident
|
|
823
|
-
if worker_ident is None: # pragma: no cover - worker-only call
|
|
824
|
-
raise WriteCoordinatorError("typed command dispatch requires the writer worker")
|
|
825
|
-
capability = WriteCapability(
|
|
826
|
-
self._db_path,
|
|
827
|
-
self._owner_id,
|
|
828
|
-
worker_ident,
|
|
829
|
-
self,
|
|
830
|
-
self._capability_token,
|
|
831
|
-
)
|
|
832
|
-
result = handler(conn, capability, command)
|
|
833
|
-
if not isinstance(result, WriteResult):
|
|
834
|
-
raise WriteCoordinatorError("command handler must return WriteResult")
|
|
835
|
-
if result.command_id != command.command_id or result.kind is not command.kind:
|
|
836
|
-
raise WriteCoordinatorError(
|
|
837
|
-
"command handler returned a receipt for a different command"
|
|
838
|
-
)
|
|
839
|
-
receipt = _thaw_json(result.receipt)
|
|
840
|
-
if not isinstance(payload, dict) or not isinstance(receipt, dict):
|
|
841
|
-
raise WriteCoordinatorError("admission payload and receipt must be objects")
|
|
842
|
-
_reject_receipt_memory_content(receipt)
|
|
843
|
-
journal_id = _required_text(payload, "journal_id")
|
|
844
|
-
operation_id = _required_text(receipt, "operation_id")
|
|
845
|
-
next_sequence = int(
|
|
846
|
-
conn.execute(
|
|
847
|
-
"SELECT COALESCE(MAX(commit_sequence), 0) + 1 FROM write_commits"
|
|
848
|
-
).fetchone()[0]
|
|
849
|
-
)
|
|
850
|
-
receipt.setdefault("commit_sequence", next_sequence)
|
|
851
|
-
committed_result = WriteResult(command.command_id, command.kind, receipt)
|
|
852
|
-
receipt_json = json.dumps(
|
|
853
|
-
receipt,
|
|
854
|
-
sort_keys=True,
|
|
855
|
-
separators=(",", ":"),
|
|
856
|
-
ensure_ascii=False,
|
|
857
|
-
)
|
|
858
|
-
conn.execute(
|
|
859
|
-
"INSERT INTO write_commits("
|
|
860
|
-
"commit_sequence, command_id, journal_id, command_kind, request_hash, "
|
|
861
|
-
"profile_id, idempotency_key, operation_id, receipt_json, committed_at"
|
|
862
|
-
") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
863
|
-
(
|
|
864
|
-
next_sequence,
|
|
865
|
-
command.command_id,
|
|
866
|
-
journal_id,
|
|
867
|
-
command.kind.value,
|
|
868
|
-
request_hash,
|
|
869
|
-
profile_id,
|
|
870
|
-
idempotency_key,
|
|
871
|
-
operation_id,
|
|
872
|
-
receipt_json,
|
|
873
|
-
time.time(),
|
|
874
|
-
),
|
|
875
|
-
)
|
|
876
|
-
return committed_result
|
|
877
|
-
|
|
878
|
-
def _write_owner_metadata(self, fd: int) -> None:
|
|
879
|
-
metadata = {
|
|
880
|
-
"owner_id": self._owner_id,
|
|
881
|
-
"pid": os.getpid(),
|
|
882
|
-
"database": str(self._db_path),
|
|
883
|
-
"claimed_at_ms": int(time.time() * 1000),
|
|
884
|
-
}
|
|
885
|
-
payload = json.dumps(metadata, sort_keys=True).encode("utf-8") + b"\n"
|
|
886
|
-
os.ftruncate(fd, 0)
|
|
887
|
-
os.lseek(fd, 0, os.SEEK_SET)
|
|
888
|
-
os.write(fd, payload)
|
|
889
|
-
os.fsync(fd)
|
|
890
|
-
|
|
891
|
-
def _require_ownership(self) -> None:
|
|
892
|
-
if self._ownership_context is None:
|
|
893
|
-
raise OwnershipRequiredError("canonical writer ownership has not been claimed")
|
|
894
|
-
|
|
895
|
-
@staticmethod
|
|
896
|
-
def _coerce_lane(priority: _Priority | Lane) -> Lane:
|
|
897
|
-
try:
|
|
898
|
-
return Lane(priority)
|
|
899
|
-
except ValueError as exc:
|
|
900
|
-
raise ValueError(f"unknown coordinator priority: {priority}") from exc
|
|
901
|
-
|
|
902
|
-
@staticmethod
|
|
903
|
-
def _is_busy(error: sqlite3.Error) -> bool:
|
|
904
|
-
code = getattr(error, "sqlite_errorcode", None)
|
|
905
|
-
return code in _SQLITE_BUSY_CODES
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
def _required_text(value: Mapping[str, Any], key: str) -> str:
|
|
909
|
-
candidate = value.get(key)
|
|
910
|
-
if not isinstance(candidate, str) or not candidate:
|
|
911
|
-
raise WriteCoordinatorError(f"admission command is missing {key}")
|
|
912
|
-
return candidate
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
def _reject_receipt_memory_content(value: Any) -> None:
|
|
916
|
-
"""Keep the immutable command ledger free of deleted or edited memory text."""
|
|
917
|
-
if isinstance(value, Mapping):
|
|
918
|
-
for key, child in value.items():
|
|
919
|
-
if key.casefold() in {
|
|
920
|
-
"content",
|
|
921
|
-
"content_preview",
|
|
922
|
-
"raw_content",
|
|
923
|
-
"memory_content",
|
|
924
|
-
"source_content",
|
|
925
|
-
}:
|
|
926
|
-
raise WriteCoordinatorError(
|
|
927
|
-
"immutable command receipts must contain metadata only"
|
|
928
|
-
)
|
|
929
|
-
_reject_receipt_memory_content(child)
|
|
930
|
-
elif isinstance(value, (list, tuple)):
|
|
931
|
-
for child in value:
|
|
932
|
-
_reject_receipt_memory_content(child)
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
__all__ = [
|
|
936
|
-
"CommandConflictError",
|
|
937
|
-
"CommandKind",
|
|
938
|
-
"CommandRejectedError",
|
|
939
|
-
"Lane",
|
|
940
|
-
"OwnershipRequiredError",
|
|
941
|
-
"QueueOverloadedError",
|
|
942
|
-
"WriteCapability",
|
|
943
|
-
"WriteCommand",
|
|
944
|
-
"WriteCoordinator",
|
|
945
|
-
"WriteCoordinatorError",
|
|
946
|
-
"WriteDeadlineExceededError",
|
|
947
|
-
"WriteResult",
|
|
948
|
-
"WriterStalledError",
|
|
949
|
-
]
|