superlocalmemory 3.6.22 → 3.7.0
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/CHANGELOG.md +52 -0
- package/README.md +275 -72
- package/bin/slm-npm +43 -89
- package/docs/pi-dev-integration.md +43 -0
- package/ide/configs/antigravity-mcp.json +2 -2
- package/ide/configs/chatgpt-desktop-mcp.json +1 -1
- package/ide/configs/claude-desktop-mcp.json +2 -2
- package/ide/configs/windsurf-mcp.json +2 -2
- package/ide/hooks/context-hook.js +6 -2
- package/ide/hooks/post-recall-hook.js +7 -3
- package/ide/hooks/tool-event-hook.sh +2 -1
- package/package.json +19 -10
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/_GENERATED.md +1 -1
- package/plugin/agents/slm-memory-advisor.md +1 -1
- package/plugin/requirements.txt +1 -1
- package/plugin/skills/slm-session/SKILL.md +1 -1
- package/plugin-src/rules/AGENTS.md +1 -1
- package/pyproject.toml +40 -8
- package/scripts/postinstall-interactive.js +17 -94
- package/scripts/postinstall.js +185 -258
- package/scripts/preuninstall.js +9 -50
- package/src/superlocalmemory/__init__.py +2 -2
- package/src/superlocalmemory/attribution/mathematical_dna.py +1 -1
- package/src/superlocalmemory/attribution/signer.py +34 -19
- package/src/superlocalmemory/attribution/watermark.py +1 -1
- package/src/superlocalmemory/cli/_lazy_init.py +3 -5
- package/src/superlocalmemory/cli/commands.py +490 -195
- package/src/superlocalmemory/cli/context_commands.py +5 -4
- package/src/superlocalmemory/cli/daemon.py +282 -187
- package/src/superlocalmemory/cli/db_migrate.py +3 -1
- package/src/superlocalmemory/cli/diagnostics_cmd.py +28 -0
- package/src/superlocalmemory/cli/evidence_cmd.py +103 -0
- package/src/superlocalmemory/cli/ingest_cmd.py +7 -3
- package/src/superlocalmemory/cli/main.py +128 -31
- package/src/superlocalmemory/cli/pending_store.py +54 -38
- package/src/superlocalmemory/cli/scale_engine_cmd.py +37 -0
- package/src/superlocalmemory/cli/service_installer.py +57 -52
- package/src/superlocalmemory/cli/setup_wizard.py +142 -88
- package/src/superlocalmemory/cli/version_banner.py +2 -1
- package/src/superlocalmemory/code_graph/config.py +3 -1
- package/src/superlocalmemory/core/backend_orchestrator.py +81 -21
- package/src/superlocalmemory/core/config.py +65 -20
- package/src/superlocalmemory/core/consolidation_engine.py +9 -7
- package/src/superlocalmemory/core/context_cache.py +56 -8
- package/src/superlocalmemory/core/derivation_lineage.py +246 -0
- package/src/superlocalmemory/core/embedding_worker.py +32 -20
- package/src/superlocalmemory/core/embeddings.py +54 -18
- package/src/superlocalmemory/core/engine.py +150 -104
- package/src/superlocalmemory/core/engine_ingestion.py +513 -0
- package/src/superlocalmemory/core/engine_wiring.py +2 -0
- package/src/superlocalmemory/core/evidence_bundle.py +526 -0
- package/src/superlocalmemory/core/fact_consolidator.py +5 -11
- package/src/superlocalmemory/core/graph_analyzer.py +2 -2
- package/src/superlocalmemory/core/health_monitor.py +4 -2
- package/src/superlocalmemory/core/ingestion_command.py +636 -0
- package/src/superlocalmemory/core/injection.py +69 -18
- package/src/superlocalmemory/core/lifecycle_state.py +153 -0
- package/src/superlocalmemory/core/maintenance.py +23 -22
- package/src/superlocalmemory/core/maintenance_scheduler.py +51 -35
- package/src/superlocalmemory/core/mutations.py +143 -0
- package/src/superlocalmemory/core/platform_utils.py +7 -4
- package/src/superlocalmemory/core/ram_lock.py +16 -5
- package/src/superlocalmemory/core/rate_limit.py +1 -1
- package/src/superlocalmemory/core/recall_pipeline.py +60 -101
- package/src/superlocalmemory/core/recall_worker.py +76 -59
- package/src/superlocalmemory/core/registry.py +1 -1
- package/src/superlocalmemory/core/scale_engine.py +293 -0
- package/src/superlocalmemory/core/score_contract.py +62 -0
- package/src/superlocalmemory/core/security_primitives.py +3 -1
- package/src/superlocalmemory/core/slm_disabled.py +3 -5
- package/src/superlocalmemory/core/store_pipeline.py +172 -40
- package/src/superlocalmemory/core/tier_manager.py +32 -20
- package/src/superlocalmemory/core/worker_pool.py +13 -4
- package/src/superlocalmemory/dynamics/activation_guided_quantization.py +1 -1
- package/src/superlocalmemory/dynamics/eap_scheduler.py +10 -3
- package/src/superlocalmemory/dynamics/ebbinghaus_langevin_coupling.py +1 -1
- package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +1 -1
- package/src/superlocalmemory/encoding/auto_linker.py +1 -1
- package/src/superlocalmemory/encoding/cognitive_consolidator.py +7 -16
- package/src/superlocalmemory/encoding/consolidator.py +22 -5
- package/src/superlocalmemory/encoding/fact_extractor.py +1 -1
- package/src/superlocalmemory/encoding/foresight.py +2 -0
- package/src/superlocalmemory/encoding/graph_builder.py +1 -1
- package/src/superlocalmemory/encoding/temporal_parser.py +2 -0
- package/src/superlocalmemory/evaluation/__init__.py +13 -0
- package/src/superlocalmemory/evaluation/calibration.py +308 -0
- package/src/superlocalmemory/evolution/skill_evolver.py +2 -1
- package/src/superlocalmemory/graph/cozo_backend.py +256 -23
- package/src/superlocalmemory/hooks/_outcome_common.py +21 -11
- package/src/superlocalmemory/hooks/antigravity_adapter.py +10 -31
- package/src/superlocalmemory/hooks/auto_invoker.py +25 -27
- package/src/superlocalmemory/hooks/auto_recall.py +31 -6
- package/src/superlocalmemory/hooks/auto_recall_hook.py +13 -33
- package/src/superlocalmemory/hooks/before_web_hook.py +9 -7
- package/src/superlocalmemory/hooks/claude_code_hooks.py +126 -39
- package/src/superlocalmemory/hooks/codex_assets.py +59 -0
- package/src/superlocalmemory/hooks/codex_hooks.py +186 -0
- package/src/superlocalmemory/hooks/context_payload.py +1 -1
- package/src/superlocalmemory/hooks/copilot_adapter.py +9 -24
- package/src/superlocalmemory/hooks/cursor_adapter.py +10 -32
- package/src/superlocalmemory/hooks/hook_daemon.py +4 -2
- package/src/superlocalmemory/hooks/hook_handlers.py +241 -55
- package/src/superlocalmemory/hooks/memory_protocol.py +5 -3
- package/src/superlocalmemory/hooks/post_tool_async_hook.py +4 -2
- package/src/superlocalmemory/hooks/session_registry.py +15 -8
- package/src/superlocalmemory/hooks/stop_outcome_hook.py +10 -6
- package/src/superlocalmemory/hooks/topic_shift_hook.py +42 -12
- package/src/superlocalmemory/hooks/user_prompt_hook.py +9 -14
- package/src/superlocalmemory/hooks/user_prompt_rehash_hook.py +19 -11
- package/src/superlocalmemory/infra/auth_middleware.py +38 -5
- package/src/superlocalmemory/infra/backup.py +7 -5
- package/src/superlocalmemory/infra/cloud_backup.py +18 -8
- package/src/superlocalmemory/infra/daemon_identity.py +248 -0
- package/src/superlocalmemory/infra/data_root.py +199 -0
- package/src/superlocalmemory/infra/event_bus.py +3 -1
- package/src/superlocalmemory/infra/local_diagnostics.py +327 -0
- package/src/superlocalmemory/infra/process_reaper.py +23 -0
- package/src/superlocalmemory/ingestion/adapter_manager.py +27 -9
- package/src/superlocalmemory/ingestion/base_adapter.py +25 -31
- package/src/superlocalmemory/ingestion/calendar_adapter.py +13 -4
- package/src/superlocalmemory/ingestion/credentials.py +14 -7
- package/src/superlocalmemory/ingestion/gmail_adapter.py +13 -4
- package/src/superlocalmemory/ingestion/transcript_adapter.py +7 -2
- package/src/superlocalmemory/learning/consolidation_quantization_worker.py +1 -1
- package/src/superlocalmemory/learning/ensemble.py +11 -0
- package/src/superlocalmemory/learning/entity_compiler.py +1 -1
- package/src/superlocalmemory/learning/feedback.py +1 -1
- package/src/superlocalmemory/learning/forgetting_scheduler.py +12 -7
- package/src/superlocalmemory/learning/quantization_scheduler.py +1 -1
- package/src/superlocalmemory/learning/ranker.py +4 -1
- package/src/superlocalmemory/learning/source_quality.py +1 -1
- package/src/superlocalmemory/learning/trigram_index.py +3 -2
- package/src/superlocalmemory/llm/backbone.py +13 -8
- package/src/superlocalmemory/math/ebbinghaus.py +1 -1
- package/src/superlocalmemory/math/fisher.py +1 -1
- package/src/superlocalmemory/math/fisher_quantized.py +1 -1
- package/src/superlocalmemory/math/hopfield.py +1 -1
- package/src/superlocalmemory/math/langevin.py +1 -1
- package/src/superlocalmemory/math/polar_quant.py +3 -4
- package/src/superlocalmemory/math/qjl.py +1 -1
- package/src/superlocalmemory/math/sheaf.py +1 -1
- package/src/superlocalmemory/math/turbo_quant.py +3 -2
- package/src/superlocalmemory/mcp/_daemon_proxy.py +12 -11
- package/src/superlocalmemory/mcp/_pool_adapter.py +27 -0
- package/src/superlocalmemory/mcp/http_transport.py +53 -0
- package/src/superlocalmemory/mcp/server.py +39 -13
- package/src/superlocalmemory/mcp/shared.py +69 -3
- package/src/superlocalmemory/mcp/tools_active.py +141 -31
- package/src/superlocalmemory/mcp/tools_core.py +128 -29
- package/src/superlocalmemory/mcp/tools_evolution.py +5 -7
- package/src/superlocalmemory/mcp/tools_learning.py +42 -2
- package/src/superlocalmemory/mcp/tools_mesh.py +7 -23
- package/src/superlocalmemory/mcp/tools_optimize.py +8 -1
- package/src/superlocalmemory/mcp/tools_v28.py +23 -2
- package/src/superlocalmemory/mcp/tools_v3.py +26 -1
- package/src/superlocalmemory/mcp/tools_v33.py +56 -17
- package/src/superlocalmemory/mesh/broker.py +2 -0
- package/src/superlocalmemory/mesh/remote_sync.py +50 -12
- package/src/superlocalmemory/optimize/cache/manager.py +77 -1
- package/src/superlocalmemory/optimize/cache/semantic.py +23 -3
- package/src/superlocalmemory/optimize/compress/ccr.py +4 -0
- package/src/superlocalmemory/optimize/compress/router.py +6 -1
- package/src/superlocalmemory/optimize/config/__init__.py +5 -0
- package/src/superlocalmemory/optimize/config/store.py +6 -4
- package/src/superlocalmemory/optimize/proxy/_helpers.py +15 -5
- package/src/superlocalmemory/optimize/proxy/capture.py +3 -2
- package/src/superlocalmemory/optimize/proxy/server.py +2 -2
- package/src/superlocalmemory/optimize/storage/db.py +14 -13
- package/src/superlocalmemory/retrieval/agentic.py +1 -1
- package/src/superlocalmemory/retrieval/ann_index.py +1 -1
- package/src/superlocalmemory/retrieval/bm25_channel.py +35 -11
- package/src/superlocalmemory/retrieval/bridge_discovery.py +73 -8
- package/src/superlocalmemory/retrieval/engine.py +169 -79
- package/src/superlocalmemory/retrieval/entity_channel.py +289 -67
- package/src/superlocalmemory/retrieval/forgetting_filter.py +1 -1
- package/src/superlocalmemory/retrieval/fusion.py +1 -1
- package/src/superlocalmemory/retrieval/hopfield_channel.py +118 -30
- package/src/superlocalmemory/retrieval/profile_channel.py +1 -1
- package/src/superlocalmemory/retrieval/quantization_aware_search.py +16 -10
- package/src/superlocalmemory/retrieval/reranker.py +56 -20
- package/src/superlocalmemory/retrieval/scope_policy.py +85 -0
- package/src/superlocalmemory/retrieval/semantic_channel.py +122 -14
- package/src/superlocalmemory/retrieval/spreading_activation.py +141 -25
- package/src/superlocalmemory/retrieval/strategy.py +1 -1
- package/src/superlocalmemory/retrieval/temporal_channel.py +30 -15
- package/src/superlocalmemory/retrieval/vector_store.py +1 -1
- package/src/superlocalmemory/server/api.py +10 -7
- package/src/superlocalmemory/server/bandit_loops.py +4 -2
- package/src/superlocalmemory/server/recall_serializer.py +24 -0
- package/src/superlocalmemory/server/route_mutations.py +84 -0
- package/src/superlocalmemory/server/routes/agents.py +8 -6
- package/src/superlocalmemory/server/routes/brain.py +14 -12
- package/src/superlocalmemory/server/routes/chat.py +29 -12
- package/src/superlocalmemory/server/routes/data_io.py +55 -24
- package/src/superlocalmemory/server/routes/helpers.py +29 -4
- package/src/superlocalmemory/server/routes/ingest.py +53 -36
- package/src/superlocalmemory/server/routes/memories.py +104 -43
- package/src/superlocalmemory/server/routes/mesh.py +31 -0
- package/src/superlocalmemory/server/routes/profiles.py +26 -4
- package/src/superlocalmemory/server/routes/tiers.py +43 -11
- package/src/superlocalmemory/server/routes/timeline.py +5 -1
- package/src/superlocalmemory/server/routes/v3_api.py +76 -21
- package/src/superlocalmemory/server/security_middleware.py +1 -1
- package/src/superlocalmemory/server/ui.py +6 -3
- package/src/superlocalmemory/server/unified_daemon.py +680 -293
- package/src/superlocalmemory/server/write_identity.py +147 -0
- package/src/superlocalmemory/storage/access_log.py +4 -3
- package/src/superlocalmemory/storage/database.py +118 -25
- package/src/superlocalmemory/storage/migration_runner.py +84 -1
- package/src/superlocalmemory/storage/migration_v33.py +1 -1
- package/src/superlocalmemory/storage/migrations/M002_model_state_history.py +6 -60
- package/src/superlocalmemory/storage/migrations/M018_ingestion_operations.py +120 -0
- package/src/superlocalmemory/storage/migrations/M019_derivation_lineage.py +54 -0
- package/src/superlocalmemory/storage/migrations/M020_model_state_integrity.py +52 -0
- package/src/superlocalmemory/storage/migrations/__init__.py +5 -0
- package/src/superlocalmemory/storage/models.py +16 -0
- package/src/superlocalmemory/storage/quantized_store.py +20 -3
- package/src/superlocalmemory/storage/v2_migrator.py +5 -3
- package/src/superlocalmemory/ui/favicon.svg +5 -0
- package/src/superlocalmemory/ui/index.html +1 -0
- package/src/superlocalmemory/ui/js/compliance.js +1 -1
- package/src/superlocalmemory/ui/js/core.js +49 -8
- package/src/superlocalmemory/ui/js/dashboard.js +23 -2
- package/src/superlocalmemory/ui/js/feedback.js +1 -1
- package/src/superlocalmemory/ui/js/graph-filters.js +1 -1
- package/src/superlocalmemory/ui/js/graph-ui.js +1 -1
- package/src/superlocalmemory/ui/js/lifecycle.js +1 -1
- package/src/superlocalmemory/ui/js/ng-mesh.js +15 -49
- package/src/superlocalmemory/ui/js/settings.js +4 -2
- package/src/superlocalmemory/vector/lancedb_backend.py +57 -9
- package/bin/slm +0 -59
- package/bin/slm.bat +0 -77
- package/bin/slm.cmd +0 -5
- package/ide/integrations/langchain/README.md +0 -106
- package/ide/integrations/langchain/langchain_superlocalmemory/__init__.py +0 -9
- package/ide/integrations/langchain/langchain_superlocalmemory/chat_message_history.py +0 -201
- package/ide/integrations/langchain/pyproject.toml +0 -38
- package/ide/integrations/langchain/tests/__init__.py +0 -3
- package/ide/integrations/langchain/tests/test_chat_message_history.py +0 -215
- package/ide/integrations/langchain/tests/test_security.py +0 -117
- package/ide/integrations/llamaindex/README.md +0 -81
- package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/__init__.py +0 -9
- package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/base.py +0 -316
- package/ide/integrations/llamaindex/pyproject.toml +0 -43
- package/ide/integrations/llamaindex/tests/__init__.py +0 -3
- package/ide/integrations/llamaindex/tests/test_chat_store.py +0 -294
- package/ide/integrations/llamaindex/tests/test_security.py +0 -241
- package/plugin-src/.mcp.json +0 -12
- package/plugin-src/agents/slm-memory-advisor.md +0 -44
- package/plugin-src/agents/slm-optimize-advisor.md +0 -38
- package/plugin-src/hooks/.gitkeep +0 -0
- package/plugin-src/hooks/hooks.json +0 -23
- package/plugin-src/manifest.json +0 -25
- package/plugin-src/requirements.txt +0 -1
- package/plugin-src/rules/CLAUDE.md.fragment +0 -44
- package/plugin-src/scripts/ensure-venv.bat +0 -122
- package/plugin-src/scripts/ensure-venv.sh +0 -105
- package/plugin-src/scripts/slm-launch +0 -15
- package/plugin-src/scripts/slm-launch.bat +0 -17
- package/plugin-src/settings.json +0 -16
- package/plugin-src/skills/slm-cache/SKILL.md +0 -140
- package/plugin-src/skills/slm-compress/SKILL.md +0 -143
- package/plugin-src/skills/slm-graph/SKILL.md +0 -300
- package/plugin-src/skills/slm-recall/SKILL.md +0 -204
- package/plugin-src/skills/slm-remember/SKILL.md +0 -194
- package/plugin-src/skills/slm-session/SKILL.md +0 -207
- package/plugin-src/skills/slm-status/SKILL.md +0 -149
- package/scripts/__tests__/build-plugin.test.mjs +0 -613
- package/scripts/_savings_math.py +0 -270
- package/scripts/build-dmg.sh +0 -417
- package/scripts/build-plugin.js +0 -742
- package/scripts/build-slm-hook.ps1 +0 -40
- package/scripts/build-slm-hook.sh +0 -45
- package/scripts/build_entry.py +0 -452
- package/scripts/ci/stage5b_gate.sh +0 -50
- package/scripts/dogfood_savings.py +0 -490
- package/scripts/generate-thumbnails.py +0 -218
- package/scripts/install-skills.ps1 +0 -4
- package/scripts/install-skills.sh +0 -5
- package/scripts/install.ps1 +0 -701
- package/scripts/install.sh +0 -1015
- package/scripts/postinstall_binary.js +0 -287
- package/scripts/prepack.js +0 -33
- package/scripts/release_manifest.py +0 -273
- package/scripts/slm-hook.spec +0 -56
- package/scripts/start-dashboard.ps1 +0 -52
- package/scripts/start-dashboard.sh +0 -41
- package/scripts/sync-wiki.ps1 +0 -127
- package/scripts/sync-wiki.sh +0 -82
- package/scripts/test-dmg.sh +0 -161
- package/scripts/test-npm-package.ps1 +0 -252
- package/scripts/test-npm-package.sh +0 -207
- package/scripts/verify-install.ps1 +0 -294
- package/scripts/verify-install.sh +0 -266
- package/scripts/verify-v27.ps1 +0 -301
- package/scripts/verify-v27.sh +0 -233
- package/src/superlocalmemory.egg-info/PKG-INFO +0 -513
- package/src/superlocalmemory.egg-info/SOURCES.txt +0 -529
- package/src/superlocalmemory.egg-info/dependency_links.txt +0 -1
- package/src/superlocalmemory.egg-info/entry_points.txt +0 -2
- package/src/superlocalmemory.egg-info/requires.txt +0 -71
- package/src/superlocalmemory.egg-info/top_level.txt +0 -1
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
|
+
# Licensed under AGPL-3.0-or-later
|
|
3
|
+
|
|
4
|
+
"""Shared HTTP write-identity boundary.
|
|
5
|
+
|
|
6
|
+
Caller-selected IDE or agent labels are audit metadata. Authorization is
|
|
7
|
+
derived only from the private capability for the exact daemon instance or the
|
|
8
|
+
local install token used by the same-origin dashboard.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import hashlib
|
|
14
|
+
import hmac
|
|
15
|
+
import os
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
from fastapi import HTTPException, Request
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _header(request: Request, name: str) -> str:
|
|
22
|
+
headers = request.headers
|
|
23
|
+
value = headers.get(name, "")
|
|
24
|
+
if value:
|
|
25
|
+
return str(value)
|
|
26
|
+
lowered = name.lower()
|
|
27
|
+
for key, candidate in headers.items():
|
|
28
|
+
if str(key).lower() == lowered:
|
|
29
|
+
return str(candidate)
|
|
30
|
+
return ""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def require_daemon_actor(request: Request, descriptor: Any | None) -> str:
|
|
34
|
+
"""Require the private capability for one exact daemon instance."""
|
|
35
|
+
capability = _header(request, "X-SLM-Daemon-Capability")
|
|
36
|
+
if descriptor is None or not hmac.compare_digest(
|
|
37
|
+
capability,
|
|
38
|
+
str(descriptor.capability),
|
|
39
|
+
):
|
|
40
|
+
raise HTTPException(403, detail="Invalid daemon capability")
|
|
41
|
+
target_instance = _header(request, "X-SLM-Target-Instance")
|
|
42
|
+
if not hmac.compare_digest(
|
|
43
|
+
target_instance,
|
|
44
|
+
str(descriptor.instance_id),
|
|
45
|
+
):
|
|
46
|
+
raise HTTPException(409, detail="Daemon instance changed")
|
|
47
|
+
return f"daemon-capability:{descriptor.capability_fingerprint}"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def require_write_actor(
|
|
51
|
+
request: Request,
|
|
52
|
+
descriptor: Any | None,
|
|
53
|
+
*,
|
|
54
|
+
actor_kind: str = "dashboard",
|
|
55
|
+
) -> str:
|
|
56
|
+
"""Return a capability-derived actor or reject the write."""
|
|
57
|
+
if _header(request, "X-SLM-Daemon-Capability"):
|
|
58
|
+
return require_daemon_actor(request, descriptor)
|
|
59
|
+
|
|
60
|
+
from superlocalmemory.core.security_primitives import verify_install_token
|
|
61
|
+
|
|
62
|
+
if verify_install_token(_header(request, "X-Install-Token")):
|
|
63
|
+
from superlocalmemory.core.engine_ingestion import local_trusted_actor_id
|
|
64
|
+
|
|
65
|
+
return local_trusted_actor_id(actor_kind)
|
|
66
|
+
|
|
67
|
+
from superlocalmemory.infra.auth_middleware import verify_api_key
|
|
68
|
+
|
|
69
|
+
api_key = _header(request, "X-SLM-API-Key")
|
|
70
|
+
if verify_api_key(api_key):
|
|
71
|
+
fingerprint = hashlib.sha256(
|
|
72
|
+
b"superlocalmemory-api-actor-v1\0" + api_key.encode("utf-8")
|
|
73
|
+
).hexdigest()
|
|
74
|
+
return f"api-key:{actor_kind}:{fingerprint}"
|
|
75
|
+
|
|
76
|
+
raise HTTPException(403, detail="Authenticated write capability required")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def require_http_mutation_actor(
|
|
80
|
+
request: Request,
|
|
81
|
+
descriptor: Any | None,
|
|
82
|
+
*,
|
|
83
|
+
actor_kind: str = "http-route",
|
|
84
|
+
mesh_secret: str | None = None,
|
|
85
|
+
) -> str:
|
|
86
|
+
"""Derive a principal for any state-changing HTTP operation.
|
|
87
|
+
|
|
88
|
+
Private credentials always take precedence. An uncredentialed loopback
|
|
89
|
+
peer is the same local-user boundary as the filesystem capability and gets
|
|
90
|
+
a derived local actor. Non-loopback callers fail closed. Mesh credentials
|
|
91
|
+
are accepted only when the broker has an explicit shared secret.
|
|
92
|
+
"""
|
|
93
|
+
credential_headers = (
|
|
94
|
+
"X-SLM-Daemon-Capability",
|
|
95
|
+
"X-Install-Token",
|
|
96
|
+
"X-SLM-API-Key",
|
|
97
|
+
)
|
|
98
|
+
if any(_header(request, name) for name in credential_headers):
|
|
99
|
+
return require_write_actor(
|
|
100
|
+
request,
|
|
101
|
+
descriptor,
|
|
102
|
+
actor_kind=actor_kind,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
client_host = request.client.host if request.client else ""
|
|
106
|
+
is_test_client = (
|
|
107
|
+
client_host == "testclient"
|
|
108
|
+
and os.environ.get("SLM_TEST_ISOLATION") == "1"
|
|
109
|
+
)
|
|
110
|
+
if client_host in ("127.0.0.1", "::1", "localhost") or is_test_client:
|
|
111
|
+
from superlocalmemory.core.engine_ingestion import local_trusted_actor_id
|
|
112
|
+
|
|
113
|
+
return local_trusted_actor_id(actor_kind)
|
|
114
|
+
|
|
115
|
+
if mesh_secret:
|
|
116
|
+
presented = (
|
|
117
|
+
_header(request, "X-Mesh-Secret")
|
|
118
|
+
or _header(request, "Authorization").removeprefix("Bearer ").strip()
|
|
119
|
+
)
|
|
120
|
+
if presented and hmac.compare_digest(presented, mesh_secret):
|
|
121
|
+
fingerprint = hashlib.sha256(
|
|
122
|
+
b"superlocalmemory-mesh-actor-v1\0" + presented.encode("utf-8")
|
|
123
|
+
).hexdigest()
|
|
124
|
+
return f"mesh-secret:{fingerprint}"
|
|
125
|
+
|
|
126
|
+
raise HTTPException(403, detail="Authenticated mutation capability required")
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def authenticated_request_actor(
|
|
130
|
+
request: Request,
|
|
131
|
+
descriptor: Any | None = None,
|
|
132
|
+
*,
|
|
133
|
+
actor_kind: str = "http-route",
|
|
134
|
+
) -> str:
|
|
135
|
+
"""Return the middleware-verified principal or verify route credentials."""
|
|
136
|
+
actor = getattr(request.state, "authenticated_actor", "")
|
|
137
|
+
if actor:
|
|
138
|
+
return str(actor)
|
|
139
|
+
return require_write_actor(request, descriptor, actor_kind=actor_kind)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
__all__ = [
|
|
143
|
+
"require_daemon_actor",
|
|
144
|
+
"authenticated_request_actor",
|
|
145
|
+
"require_http_mutation_actor",
|
|
146
|
+
"require_write_actor",
|
|
147
|
+
]
|
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
# Licensed under AGPL-3.0-or-later - see LICENSE file
|
|
3
3
|
# Part of SuperLocalMemory V3
|
|
4
4
|
|
|
5
|
-
"""
|
|
5
|
+
"""Exposure log for fact retrieval events.
|
|
6
6
|
|
|
7
7
|
Tracks when facts are accessed (recall, auto_invoke, search).
|
|
8
|
-
|
|
8
|
+
The historical table name is retained for compatibility. A ``recall`` row is
|
|
9
|
+
exposure telemetry only and must never be interpreted as positive feedback.
|
|
9
10
|
All SQL parameterized (Rule 11). Silent errors (Rule 19).
|
|
10
11
|
|
|
11
12
|
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
12
|
-
License:
|
|
13
|
+
License: AGPL-3.0-or-later
|
|
13
14
|
"""
|
|
14
15
|
|
|
15
16
|
from __future__ import annotations
|
|
@@ -130,7 +130,12 @@ class DatabaseManager:
|
|
|
130
130
|
self.db_path = Path(db_path)
|
|
131
131
|
self.db_path.parent.mkdir(parents=True, exist_ok=True)
|
|
132
132
|
self._lock = threading.Lock()
|
|
133
|
-
|
|
133
|
+
# Transaction connections are thread-affine in sqlite3. A manager is
|
|
134
|
+
# shared across HTTP, materializer, and worker threads, so a process-
|
|
135
|
+
# global connection slot lets another thread accidentally execute on
|
|
136
|
+
# an uncommitted foreign connection. Keep the active connection local
|
|
137
|
+
# to the thread that owns the transaction.
|
|
138
|
+
self._txn_state = threading.local()
|
|
134
139
|
self._enable_wal()
|
|
135
140
|
|
|
136
141
|
def _enable_wal(self) -> None:
|
|
@@ -174,7 +179,7 @@ class DatabaseManager:
|
|
|
174
179
|
"""Atomic transaction. All writes commit or rollback together."""
|
|
175
180
|
with self._lock:
|
|
176
181
|
conn = self._connect()
|
|
177
|
-
self.
|
|
182
|
+
self._txn_state.conn = conn
|
|
178
183
|
try:
|
|
179
184
|
yield
|
|
180
185
|
conn.commit()
|
|
@@ -182,7 +187,7 @@ class DatabaseManager:
|
|
|
182
187
|
conn.rollback()
|
|
183
188
|
raise
|
|
184
189
|
finally:
|
|
185
|
-
self.
|
|
190
|
+
self._txn_state.conn = None
|
|
186
191
|
conn.close()
|
|
187
192
|
|
|
188
193
|
@contextmanager
|
|
@@ -196,7 +201,7 @@ class DatabaseManager:
|
|
|
196
201
|
"""
|
|
197
202
|
with self._lock:
|
|
198
203
|
conn = self._connect()
|
|
199
|
-
self.
|
|
204
|
+
self._txn_state.conn = conn
|
|
200
205
|
try:
|
|
201
206
|
yield conn
|
|
202
207
|
conn.commit()
|
|
@@ -204,7 +209,7 @@ class DatabaseManager:
|
|
|
204
209
|
conn.rollback()
|
|
205
210
|
raise
|
|
206
211
|
finally:
|
|
207
|
-
self.
|
|
212
|
+
self._txn_state.conn = None
|
|
208
213
|
conn.close()
|
|
209
214
|
|
|
210
215
|
def execute(self, sql: str, params: tuple[Any, ...] = ()) -> list[sqlite3.Row]:
|
|
@@ -212,8 +217,9 @@ class DatabaseManager:
|
|
|
212
217
|
|
|
213
218
|
Uses shared conn inside transaction, else per-call with retry.
|
|
214
219
|
"""
|
|
215
|
-
|
|
216
|
-
|
|
220
|
+
transaction_conn = getattr(self._txn_state, "conn", None)
|
|
221
|
+
if transaction_conn is not None:
|
|
222
|
+
return transaction_conn.execute(sql, params).fetchall()
|
|
217
223
|
|
|
218
224
|
last_error: Exception | None = None
|
|
219
225
|
for attempt in range(_MAX_RETRIES):
|
|
@@ -438,6 +444,35 @@ class DatabaseManager:
|
|
|
438
444
|
)
|
|
439
445
|
return [self._row_to_fact(r) for r in rows]
|
|
440
446
|
|
|
447
|
+
def get_external_visible_facts(
|
|
448
|
+
self,
|
|
449
|
+
profile_id: str,
|
|
450
|
+
*,
|
|
451
|
+
include_global: bool = False,
|
|
452
|
+
include_shared: bool = False,
|
|
453
|
+
) -> list[AtomicFact]:
|
|
454
|
+
"""Cross-profile facts visible to ``profile_id`` under scope policy.
|
|
455
|
+
|
|
456
|
+
This is the bounded supplement used by profile-partitioned candidate
|
|
457
|
+
indexes. It deliberately excludes the requester's own partition so a
|
|
458
|
+
fast local index can merge only the global/authorized-shared rows it
|
|
459
|
+
cannot discover itself. The canonical scope predicate remains the
|
|
460
|
+
sole authorization rule.
|
|
461
|
+
"""
|
|
462
|
+
if not include_global and not include_shared:
|
|
463
|
+
return []
|
|
464
|
+
where, params = _scope_where(
|
|
465
|
+
profile_id,
|
|
466
|
+
include_global=include_global,
|
|
467
|
+
include_shared=include_shared,
|
|
468
|
+
)
|
|
469
|
+
rows = self.execute(
|
|
470
|
+
f"SELECT * FROM atomic_facts WHERE {where} AND profile_id != ? "
|
|
471
|
+
"ORDER BY created_at DESC",
|
|
472
|
+
(*params, profile_id),
|
|
473
|
+
)
|
|
474
|
+
return [self._row_to_fact(r) for r in rows]
|
|
475
|
+
|
|
441
476
|
_MAX_FACTS_PER_ENTITY_LOOKUP: int = 100
|
|
442
477
|
|
|
443
478
|
def get_facts_by_entity(
|
|
@@ -755,11 +790,35 @@ class DatabaseManager:
|
|
|
755
790
|
(fact_id, profile_id, json.dumps(tokens)),
|
|
756
791
|
)
|
|
757
792
|
|
|
758
|
-
def get_all_bm25_tokens(
|
|
759
|
-
|
|
793
|
+
def get_all_bm25_tokens(
|
|
794
|
+
self,
|
|
795
|
+
profile_id: str,
|
|
796
|
+
include_global: bool = False,
|
|
797
|
+
include_shared: bool = False,
|
|
798
|
+
) -> dict[str, list[str]]:
|
|
799
|
+
"""Load the visible legacy BM25 index: fact_id -> token list."""
|
|
800
|
+
if not include_global and not include_shared:
|
|
801
|
+
# Preserve the historical token-store contract, including repair
|
|
802
|
+
# tooling that can inspect orphaned token rows before facts exist.
|
|
803
|
+
rows = self.execute(
|
|
804
|
+
"SELECT fact_id, tokens FROM bm25_tokens WHERE profile_id = ?",
|
|
805
|
+
(profile_id,),
|
|
806
|
+
)
|
|
807
|
+
return {
|
|
808
|
+
dict(row)["fact_id"]: json.loads(dict(row)["tokens"])
|
|
809
|
+
for row in rows
|
|
810
|
+
}
|
|
811
|
+
where, params = _scope_where(
|
|
812
|
+
profile_id,
|
|
813
|
+
include_global=include_global,
|
|
814
|
+
include_shared=include_shared,
|
|
815
|
+
prefix="af",
|
|
816
|
+
)
|
|
760
817
|
rows = self.execute(
|
|
761
|
-
"SELECT fact_id, tokens FROM bm25_tokens
|
|
762
|
-
|
|
818
|
+
"SELECT bt.fact_id, bt.tokens FROM bm25_tokens AS bt "
|
|
819
|
+
"JOIN atomic_facts AS af ON af.fact_id = bt.fact_id "
|
|
820
|
+
f"WHERE {where}",
|
|
821
|
+
(*params,),
|
|
763
822
|
)
|
|
764
823
|
return {dict(r)["fact_id"]: json.loads(dict(r)["tokens"]) for r in rows}
|
|
765
824
|
|
|
@@ -1383,6 +1442,36 @@ class DatabaseManager:
|
|
|
1383
1442
|
Retries 3x on SQLITE_BUSY (handled by execute()).
|
|
1384
1443
|
All SQL parameterized (HR-05).
|
|
1385
1444
|
"""
|
|
1445
|
+
from superlocalmemory.core.lifecycle_state import atomic_lifecycle_for
|
|
1446
|
+
|
|
1447
|
+
with self.transaction():
|
|
1448
|
+
self._upsert_retention_in_transaction(
|
|
1449
|
+
fact_id=fact_id,
|
|
1450
|
+
profile_id=profile_id,
|
|
1451
|
+
retention_score=retention_score,
|
|
1452
|
+
memory_strength=memory_strength,
|
|
1453
|
+
access_count=access_count,
|
|
1454
|
+
last_accessed_at=last_accessed_at,
|
|
1455
|
+
lifecycle_zone=lifecycle_zone,
|
|
1456
|
+
)
|
|
1457
|
+
self.execute(
|
|
1458
|
+
"UPDATE atomic_facts SET lifecycle = ? "
|
|
1459
|
+
"WHERE fact_id = ? AND profile_id = ?",
|
|
1460
|
+
(atomic_lifecycle_for(lifecycle_zone), fact_id, profile_id),
|
|
1461
|
+
)
|
|
1462
|
+
|
|
1463
|
+
def _upsert_retention_in_transaction(
|
|
1464
|
+
self,
|
|
1465
|
+
*,
|
|
1466
|
+
fact_id: str,
|
|
1467
|
+
profile_id: str,
|
|
1468
|
+
retention_score: float,
|
|
1469
|
+
memory_strength: float,
|
|
1470
|
+
access_count: int,
|
|
1471
|
+
last_accessed_at: str,
|
|
1472
|
+
lifecycle_zone: str,
|
|
1473
|
+
) -> None:
|
|
1474
|
+
"""Write one retention row using the caller's active transaction."""
|
|
1386
1475
|
self.execute(
|
|
1387
1476
|
"INSERT INTO fact_retention "
|
|
1388
1477
|
"(fact_id, profile_id, retention_score, memory_strength, "
|
|
@@ -1409,9 +1498,11 @@ class DatabaseManager:
|
|
|
1409
1498
|
Returns count of successfully upserted rows.
|
|
1410
1499
|
"""
|
|
1411
1500
|
count = 0
|
|
1501
|
+
from superlocalmemory.core.lifecycle_state import atomic_lifecycle_for
|
|
1502
|
+
|
|
1412
1503
|
with self.transaction():
|
|
1413
1504
|
for f in facts:
|
|
1414
|
-
self.
|
|
1505
|
+
self._upsert_retention_in_transaction(
|
|
1415
1506
|
fact_id=f["fact_id"],
|
|
1416
1507
|
profile_id=profile_id,
|
|
1417
1508
|
retention_score=f["retention"],
|
|
@@ -1420,6 +1511,11 @@ class DatabaseManager:
|
|
|
1420
1511
|
last_accessed_at=f["last_accessed_at"],
|
|
1421
1512
|
lifecycle_zone=f["zone"],
|
|
1422
1513
|
)
|
|
1514
|
+
self.execute(
|
|
1515
|
+
"UPDATE atomic_facts SET lifecycle = ? "
|
|
1516
|
+
"WHERE fact_id = ? AND profile_id = ?",
|
|
1517
|
+
(atomic_lifecycle_for(f["zone"]), f["fact_id"], profile_id),
|
|
1518
|
+
)
|
|
1423
1519
|
count += 1
|
|
1424
1520
|
return count
|
|
1425
1521
|
|
|
@@ -1464,20 +1560,17 @@ class DatabaseManager:
|
|
|
1464
1560
|
)
|
|
1465
1561
|
return
|
|
1466
1562
|
|
|
1467
|
-
|
|
1468
|
-
self.execute(
|
|
1469
|
-
"UPDATE fact_retention SET lifecycle_zone = 'forgotten', "
|
|
1470
|
-
" retention_score = 0.0 "
|
|
1471
|
-
"WHERE fact_id = ? AND profile_id = ?",
|
|
1472
|
-
(fact_id, profile_id),
|
|
1473
|
-
)
|
|
1563
|
+
from superlocalmemory.core.lifecycle_state import set_fact_lifecycle_zone
|
|
1474
1564
|
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
(
|
|
1480
|
-
|
|
1565
|
+
with self.transaction():
|
|
1566
|
+
set_fact_lifecycle_zone(
|
|
1567
|
+
self, [fact_id], "forgotten", profile_id=profile_id,
|
|
1568
|
+
)
|
|
1569
|
+
self.execute(
|
|
1570
|
+
"UPDATE fact_retention SET retention_score = 0.0 "
|
|
1571
|
+
"WHERE fact_id = ? AND profile_id = ?",
|
|
1572
|
+
(fact_id, profile_id),
|
|
1573
|
+
)
|
|
1481
1574
|
|
|
1482
1575
|
# ------------------------------------------------------------------
|
|
1483
1576
|
# Phase E: CCQ Consolidated Blocks & Audit CRUD
|
|
@@ -34,25 +34,64 @@ import sqlite3
|
|
|
34
34
|
from dataclasses import dataclass, field
|
|
35
35
|
from datetime import datetime, timezone
|
|
36
36
|
from pathlib import Path
|
|
37
|
-
from typing import Iterable
|
|
38
37
|
|
|
39
38
|
from superlocalmemory.storage.migrations import (
|
|
40
39
|
M001_add_signal_features_columns as _M001,
|
|
40
|
+
)
|
|
41
|
+
from superlocalmemory.storage.migrations import (
|
|
41
42
|
M002_model_state_history as _M002,
|
|
43
|
+
)
|
|
44
|
+
from superlocalmemory.storage.migrations import (
|
|
42
45
|
M003_migration_log as _M003,
|
|
46
|
+
)
|
|
47
|
+
from superlocalmemory.storage.migrations import (
|
|
43
48
|
M004_cross_platform_sync_log as _M004,
|
|
49
|
+
)
|
|
50
|
+
from superlocalmemory.storage.migrations import (
|
|
44
51
|
M005_bandit_tables as _M005,
|
|
52
|
+
)
|
|
53
|
+
from superlocalmemory.storage.migrations import (
|
|
45
54
|
M006_action_outcomes_reward as _M006,
|
|
55
|
+
)
|
|
56
|
+
from superlocalmemory.storage.migrations import (
|
|
46
57
|
M007_pending_outcomes as _M007,
|
|
58
|
+
)
|
|
59
|
+
from superlocalmemory.storage.migrations import (
|
|
47
60
|
M009_model_lineage as _M009,
|
|
61
|
+
)
|
|
62
|
+
from superlocalmemory.storage.migrations import (
|
|
48
63
|
M010_evolution_config as _M010,
|
|
64
|
+
)
|
|
65
|
+
from superlocalmemory.storage.migrations import (
|
|
49
66
|
M011_archive_and_merge as _M011,
|
|
67
|
+
)
|
|
68
|
+
from superlocalmemory.storage.migrations import (
|
|
50
69
|
M012_shadow_observations as _M012,
|
|
70
|
+
)
|
|
71
|
+
from superlocalmemory.storage.migrations import (
|
|
51
72
|
M013_bi_temporal_columns as _M013,
|
|
73
|
+
)
|
|
74
|
+
from superlocalmemory.storage.migrations import (
|
|
52
75
|
M014_v345_scale_ready as _M014,
|
|
76
|
+
)
|
|
77
|
+
from superlocalmemory.storage.migrations import (
|
|
53
78
|
M015_add_pinned_column as _M015,
|
|
79
|
+
)
|
|
80
|
+
from superlocalmemory.storage.migrations import (
|
|
54
81
|
M016_add_scope_support as _M016,
|
|
55
82
|
)
|
|
83
|
+
from superlocalmemory.storage.migrations import (
|
|
84
|
+
M017_ccq_scope_column as _M017,
|
|
85
|
+
)
|
|
86
|
+
from superlocalmemory.storage.migrations import (
|
|
87
|
+
M018_ingestion_operations as _M018,
|
|
88
|
+
)
|
|
89
|
+
from superlocalmemory.storage.migrations import (
|
|
90
|
+
M019_derivation_lineage as _M019,
|
|
91
|
+
)
|
|
92
|
+
from superlocalmemory.storage.migrations import (
|
|
93
|
+
M020_model_state_integrity as _M020,
|
|
94
|
+
)
|
|
56
95
|
|
|
57
96
|
# Map migration name → module (used for the optional ``verify(conn)`` hook
|
|
58
97
|
# that lets the runner detect "already applied" state when an idempotent
|
|
@@ -73,6 +112,10 @@ _MODULES = {
|
|
|
73
112
|
_M014.NAME: _M014,
|
|
74
113
|
_M015.NAME: _M015,
|
|
75
114
|
_M016.NAME: _M016,
|
|
115
|
+
_M017.NAME: _M017,
|
|
116
|
+
_M018.NAME: _M018,
|
|
117
|
+
_M019.NAME: _M019,
|
|
118
|
+
_M020.NAME: _M020,
|
|
76
119
|
}
|
|
77
120
|
|
|
78
121
|
logger = logging.getLogger(__name__)
|
|
@@ -101,6 +144,10 @@ MIGRATIONS: list[Migration] = [
|
|
|
101
144
|
# M009 extends learning_model_state (created by M002).
|
|
102
145
|
Migration(name=_M009.NAME, db_target="learning", ddl=_M009.DDL,
|
|
103
146
|
dependencies=(_M002.NAME,)),
|
|
147
|
+
# M020 owns post-release integrity repair. M002 remains byte-for-byte
|
|
148
|
+
# compatible with databases that recorded its historical DDL hash.
|
|
149
|
+
Migration(name=_M020.NAME, db_target="learning", ddl=_M020.DDL,
|
|
150
|
+
dependencies=(_M002.NAME,)),
|
|
104
151
|
# M010 creates evolution_config + evolution_llm_cost_log (learning.db).
|
|
105
152
|
Migration(name=_M010.NAME, db_target="learning", ddl=_M010.DDL,
|
|
106
153
|
dependencies=(_M003.NAME,)),
|
|
@@ -111,6 +158,10 @@ MIGRATIONS: list[Migration] = [
|
|
|
111
158
|
Migration(name=_M004.NAME, db_target="memory", ddl=_M004.DDL),
|
|
112
159
|
# M007 creates pending_outcomes (memory.db, LLD-00 §1.2).
|
|
113
160
|
Migration(name=_M007.NAME, db_target="memory", ddl=_M007.DDL),
|
|
161
|
+
# M018 is additive and independent of runtime-bootstrapped tables.
|
|
162
|
+
Migration(name=_M018.NAME, db_target="memory", ddl=_M018.DDL),
|
|
163
|
+
Migration(name=_M019.NAME, db_target="memory", ddl=_M019.DDL,
|
|
164
|
+
dependencies=(_M018.NAME,)),
|
|
114
165
|
# M006 + M011 are deliberately NOT here — see DEFERRED_MIGRATIONS below.
|
|
115
166
|
]
|
|
116
167
|
|
|
@@ -139,6 +190,8 @@ DEFERRED_MIGRATIONS: list[Migration] = [
|
|
|
139
190
|
# M016 adds scope and shared_with columns to 5 core tables for
|
|
140
191
|
# multi-scope memory support (personal/global/shared).
|
|
141
192
|
Migration(name=_M016.NAME, db_target="memory", ddl=_M016.DDL),
|
|
193
|
+
# M017 adds scope to the engine-bootstrapped CCQ consolidation table.
|
|
194
|
+
Migration(name=_M017.NAME, db_target="memory", ddl=_M017.DDL),
|
|
142
195
|
]
|
|
143
196
|
|
|
144
197
|
|
|
@@ -395,6 +448,25 @@ def _bootstrap_both_migration_logs(
|
|
|
395
448
|
return failed, details
|
|
396
449
|
|
|
397
450
|
|
|
451
|
+
def _bootstrap_learning_schema(learning_db: Path, *, dry_run: bool) -> str | None:
|
|
452
|
+
"""Create the base learning tables before forward migrations extend them.
|
|
453
|
+
|
|
454
|
+
``apply_all`` is called by the daemon before ``MemoryEngine`` exists. A
|
|
455
|
+
blank first-install therefore has no ``learning_signals`` or
|
|
456
|
+
``learning_model_state`` tables for M001/M002/M009 to alter. The runner
|
|
457
|
+
owns this prerequisite so every caller has the same first-boot contract.
|
|
458
|
+
"""
|
|
459
|
+
if dry_run:
|
|
460
|
+
return None
|
|
461
|
+
try:
|
|
462
|
+
from superlocalmemory.learning.database import LearningDatabase
|
|
463
|
+
|
|
464
|
+
LearningDatabase(learning_db)
|
|
465
|
+
except Exception as exc: # noqa: BLE001 - retain runner's non-fatal API
|
|
466
|
+
return f"learning schema bootstrap failed: {type(exc).__name__}: {exc}"
|
|
467
|
+
return None
|
|
468
|
+
|
|
469
|
+
|
|
398
470
|
def apply_all(
|
|
399
471
|
learning_db: Path,
|
|
400
472
|
memory_db: Path,
|
|
@@ -411,6 +483,17 @@ def apply_all(
|
|
|
411
483
|
failed: list[str] = []
|
|
412
484
|
details: dict[str, str] = {}
|
|
413
485
|
|
|
486
|
+
schema_error = _bootstrap_learning_schema(learning_db, dry_run=dry_run)
|
|
487
|
+
if schema_error is not None:
|
|
488
|
+
failed.append("learning_schema_bootstrap")
|
|
489
|
+
details["learning_schema_bootstrap"] = schema_error
|
|
490
|
+
return {
|
|
491
|
+
"applied": applied,
|
|
492
|
+
"skipped": skipped,
|
|
493
|
+
"failed": failed,
|
|
494
|
+
"details": details,
|
|
495
|
+
}
|
|
496
|
+
|
|
414
497
|
# S9-W1 C3: unify the migration_log bootstrap across both DBs up-front.
|
|
415
498
|
bs_failed, bs_details = _bootstrap_both_migration_logs(
|
|
416
499
|
learning_db, memory_db, dry_run=dry_run,
|
|
@@ -21,7 +21,7 @@ by create_all_tables(). This migration module detects their absence
|
|
|
21
21
|
and creates them for databases that were created before SLM 3.3.
|
|
22
22
|
|
|
23
23
|
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
24
|
-
License:
|
|
24
|
+
License: AGPL-3.0-or-later
|
|
25
25
|
"""
|
|
26
26
|
|
|
27
27
|
from __future__ import annotations
|
|
@@ -16,7 +16,6 @@ reaching the LightGBM deserialiser.
|
|
|
16
16
|
|
|
17
17
|
from __future__ import annotations
|
|
18
18
|
|
|
19
|
-
import hashlib
|
|
20
19
|
import sqlite3
|
|
21
20
|
|
|
22
21
|
NAME = "M002_model_state_history"
|
|
@@ -40,19 +39,16 @@ def verify(conn: sqlite3.Connection) -> bool:
|
|
|
40
39
|
return _REQUIRED_COLS <= cols
|
|
41
40
|
|
|
42
41
|
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
# dev-build could have multiple rows and the partial unique index created
|
|
47
|
-
# after rebuild would fail transactionally. We now mark only the row with
|
|
48
|
-
# the MAX(id) per profile as active; everything else becomes history.
|
|
42
|
+
# IMPORTANT: this DDL shipped in V3.4.21. Migration hashes are immutable
|
|
43
|
+
# upgrade contracts, so later improvements belong in a new forward migration
|
|
44
|
+
# (M020), never in this historical definition.
|
|
49
45
|
DDL = """
|
|
50
46
|
BEGIN IMMEDIATE;
|
|
51
47
|
|
|
52
48
|
CREATE TABLE learning_model_state_new (
|
|
53
49
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
54
50
|
profile_id TEXT NOT NULL,
|
|
55
|
-
model_version TEXT NOT NULL DEFAULT '3.4.
|
|
51
|
+
model_version TEXT NOT NULL DEFAULT '3.4.21',
|
|
56
52
|
state_bytes BLOB NOT NULL,
|
|
57
53
|
bytes_sha256 TEXT NOT NULL DEFAULT '',
|
|
58
54
|
trained_on_count INTEGER NOT NULL DEFAULT 0,
|
|
@@ -65,14 +61,8 @@ CREATE TABLE learning_model_state_new (
|
|
|
65
61
|
|
|
66
62
|
INSERT INTO learning_model_state_new
|
|
67
63
|
(profile_id, state_bytes, is_active, trained_at, updated_at)
|
|
68
|
-
SELECT
|
|
69
|
-
|
|
70
|
-
SELECT MAX(lms2.id)
|
|
71
|
-
FROM learning_model_state lms2
|
|
72
|
-
WHERE lms2.profile_id = lms.profile_id
|
|
73
|
-
) THEN 1 ELSE 0 END,
|
|
74
|
-
lms.updated_at, lms.updated_at
|
|
75
|
-
FROM learning_model_state lms;
|
|
64
|
+
SELECT profile_id, state_bytes, 1, updated_at, updated_at
|
|
65
|
+
FROM learning_model_state;
|
|
76
66
|
|
|
77
67
|
DROP TABLE learning_model_state;
|
|
78
68
|
ALTER TABLE learning_model_state_new RENAME TO learning_model_state;
|
|
@@ -86,47 +76,3 @@ CREATE INDEX idx_model_profile_time
|
|
|
86
76
|
|
|
87
77
|
COMMIT;
|
|
88
78
|
"""
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
def post_ddl_hook(conn: sqlite3.Connection) -> None:
|
|
92
|
-
"""S9-W1 H-DATA-01: backfill ``bytes_sha256`` for every row copied forward.
|
|
93
|
-
|
|
94
|
-
The DDL INSERT could not list ``bytes_sha256`` because SQLite cannot
|
|
95
|
-
call a Python function inside an ``executescript`` block unless the
|
|
96
|
-
function is registered beforehand, and registering a UDF mid-DDL is
|
|
97
|
-
fragile. Instead, we run one UPDATE pass after the DDL commits.
|
|
98
|
-
|
|
99
|
-
Without this backfill, ``model_cache._parse_row`` calls
|
|
100
|
-
``verify_sha256(state_bytes, '')`` which raises IntegrityError, the
|
|
101
|
-
parser tombstones the cache entry, and EVERY 18,000+ user who had a
|
|
102
|
-
trained model on v3.4.19 loses usable learned-ranker state on upgrade.
|
|
103
|
-
|
|
104
|
-
The fix is safe: SHA-256 of the already-persisted blob is
|
|
105
|
-
deterministic, adds <1 ms per profile, and never alters
|
|
106
|
-
``state_bytes``. Runs inside the same connection so any UPDATE error
|
|
107
|
-
surfaces to the runner as ``post_ddl_hook`` failed.
|
|
108
|
-
"""
|
|
109
|
-
try:
|
|
110
|
-
rows = conn.execute(
|
|
111
|
-
"SELECT id, state_bytes FROM learning_model_state "
|
|
112
|
-
"WHERE bytes_sha256 = '' OR bytes_sha256 IS NULL"
|
|
113
|
-
).fetchall()
|
|
114
|
-
except sqlite3.Error:
|
|
115
|
-
return # table empty or schema not yet present — nothing to do.
|
|
116
|
-
|
|
117
|
-
if not rows:
|
|
118
|
-
return
|
|
119
|
-
|
|
120
|
-
updates = []
|
|
121
|
-
for row_id, state_bytes in rows:
|
|
122
|
-
if state_bytes is None:
|
|
123
|
-
continue
|
|
124
|
-
sha = hashlib.sha256(state_bytes).hexdigest()
|
|
125
|
-
updates.append((sha, row_id))
|
|
126
|
-
|
|
127
|
-
if updates:
|
|
128
|
-
conn.executemany(
|
|
129
|
-
"UPDATE learning_model_state SET bytes_sha256 = ? WHERE id = ?",
|
|
130
|
-
updates,
|
|
131
|
-
)
|
|
132
|
-
conn.commit()
|