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.
Files changed (303) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +275 -72
  3. package/bin/slm-npm +43 -89
  4. package/docs/pi-dev-integration.md +43 -0
  5. package/ide/configs/antigravity-mcp.json +2 -2
  6. package/ide/configs/chatgpt-desktop-mcp.json +1 -1
  7. package/ide/configs/claude-desktop-mcp.json +2 -2
  8. package/ide/configs/windsurf-mcp.json +2 -2
  9. package/ide/hooks/context-hook.js +6 -2
  10. package/ide/hooks/post-recall-hook.js +7 -3
  11. package/ide/hooks/tool-event-hook.sh +2 -1
  12. package/package.json +19 -10
  13. package/plugin/.claude-plugin/plugin.json +1 -1
  14. package/plugin/_GENERATED.md +1 -1
  15. package/plugin/agents/slm-memory-advisor.md +1 -1
  16. package/plugin/requirements.txt +1 -1
  17. package/plugin/skills/slm-session/SKILL.md +1 -1
  18. package/plugin-src/rules/AGENTS.md +1 -1
  19. package/pyproject.toml +40 -8
  20. package/scripts/postinstall-interactive.js +17 -94
  21. package/scripts/postinstall.js +185 -258
  22. package/scripts/preuninstall.js +9 -50
  23. package/src/superlocalmemory/__init__.py +2 -2
  24. package/src/superlocalmemory/attribution/mathematical_dna.py +1 -1
  25. package/src/superlocalmemory/attribution/signer.py +34 -19
  26. package/src/superlocalmemory/attribution/watermark.py +1 -1
  27. package/src/superlocalmemory/cli/_lazy_init.py +3 -5
  28. package/src/superlocalmemory/cli/commands.py +490 -195
  29. package/src/superlocalmemory/cli/context_commands.py +5 -4
  30. package/src/superlocalmemory/cli/daemon.py +282 -187
  31. package/src/superlocalmemory/cli/db_migrate.py +3 -1
  32. package/src/superlocalmemory/cli/diagnostics_cmd.py +28 -0
  33. package/src/superlocalmemory/cli/evidence_cmd.py +103 -0
  34. package/src/superlocalmemory/cli/ingest_cmd.py +7 -3
  35. package/src/superlocalmemory/cli/main.py +128 -31
  36. package/src/superlocalmemory/cli/pending_store.py +54 -38
  37. package/src/superlocalmemory/cli/scale_engine_cmd.py +37 -0
  38. package/src/superlocalmemory/cli/service_installer.py +57 -52
  39. package/src/superlocalmemory/cli/setup_wizard.py +142 -88
  40. package/src/superlocalmemory/cli/version_banner.py +2 -1
  41. package/src/superlocalmemory/code_graph/config.py +3 -1
  42. package/src/superlocalmemory/core/backend_orchestrator.py +81 -21
  43. package/src/superlocalmemory/core/config.py +65 -20
  44. package/src/superlocalmemory/core/consolidation_engine.py +9 -7
  45. package/src/superlocalmemory/core/context_cache.py +56 -8
  46. package/src/superlocalmemory/core/derivation_lineage.py +246 -0
  47. package/src/superlocalmemory/core/embedding_worker.py +32 -20
  48. package/src/superlocalmemory/core/embeddings.py +54 -18
  49. package/src/superlocalmemory/core/engine.py +150 -104
  50. package/src/superlocalmemory/core/engine_ingestion.py +513 -0
  51. package/src/superlocalmemory/core/engine_wiring.py +2 -0
  52. package/src/superlocalmemory/core/evidence_bundle.py +526 -0
  53. package/src/superlocalmemory/core/fact_consolidator.py +5 -11
  54. package/src/superlocalmemory/core/graph_analyzer.py +2 -2
  55. package/src/superlocalmemory/core/health_monitor.py +4 -2
  56. package/src/superlocalmemory/core/ingestion_command.py +636 -0
  57. package/src/superlocalmemory/core/injection.py +69 -18
  58. package/src/superlocalmemory/core/lifecycle_state.py +153 -0
  59. package/src/superlocalmemory/core/maintenance.py +23 -22
  60. package/src/superlocalmemory/core/maintenance_scheduler.py +51 -35
  61. package/src/superlocalmemory/core/mutations.py +143 -0
  62. package/src/superlocalmemory/core/platform_utils.py +7 -4
  63. package/src/superlocalmemory/core/ram_lock.py +16 -5
  64. package/src/superlocalmemory/core/rate_limit.py +1 -1
  65. package/src/superlocalmemory/core/recall_pipeline.py +60 -101
  66. package/src/superlocalmemory/core/recall_worker.py +76 -59
  67. package/src/superlocalmemory/core/registry.py +1 -1
  68. package/src/superlocalmemory/core/scale_engine.py +293 -0
  69. package/src/superlocalmemory/core/score_contract.py +62 -0
  70. package/src/superlocalmemory/core/security_primitives.py +3 -1
  71. package/src/superlocalmemory/core/slm_disabled.py +3 -5
  72. package/src/superlocalmemory/core/store_pipeline.py +172 -40
  73. package/src/superlocalmemory/core/tier_manager.py +32 -20
  74. package/src/superlocalmemory/core/worker_pool.py +13 -4
  75. package/src/superlocalmemory/dynamics/activation_guided_quantization.py +1 -1
  76. package/src/superlocalmemory/dynamics/eap_scheduler.py +10 -3
  77. package/src/superlocalmemory/dynamics/ebbinghaus_langevin_coupling.py +1 -1
  78. package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +1 -1
  79. package/src/superlocalmemory/encoding/auto_linker.py +1 -1
  80. package/src/superlocalmemory/encoding/cognitive_consolidator.py +7 -16
  81. package/src/superlocalmemory/encoding/consolidator.py +22 -5
  82. package/src/superlocalmemory/encoding/fact_extractor.py +1 -1
  83. package/src/superlocalmemory/encoding/foresight.py +2 -0
  84. package/src/superlocalmemory/encoding/graph_builder.py +1 -1
  85. package/src/superlocalmemory/encoding/temporal_parser.py +2 -0
  86. package/src/superlocalmemory/evaluation/__init__.py +13 -0
  87. package/src/superlocalmemory/evaluation/calibration.py +308 -0
  88. package/src/superlocalmemory/evolution/skill_evolver.py +2 -1
  89. package/src/superlocalmemory/graph/cozo_backend.py +256 -23
  90. package/src/superlocalmemory/hooks/_outcome_common.py +21 -11
  91. package/src/superlocalmemory/hooks/antigravity_adapter.py +10 -31
  92. package/src/superlocalmemory/hooks/auto_invoker.py +25 -27
  93. package/src/superlocalmemory/hooks/auto_recall.py +31 -6
  94. package/src/superlocalmemory/hooks/auto_recall_hook.py +13 -33
  95. package/src/superlocalmemory/hooks/before_web_hook.py +9 -7
  96. package/src/superlocalmemory/hooks/claude_code_hooks.py +126 -39
  97. package/src/superlocalmemory/hooks/codex_assets.py +59 -0
  98. package/src/superlocalmemory/hooks/codex_hooks.py +186 -0
  99. package/src/superlocalmemory/hooks/context_payload.py +1 -1
  100. package/src/superlocalmemory/hooks/copilot_adapter.py +9 -24
  101. package/src/superlocalmemory/hooks/cursor_adapter.py +10 -32
  102. package/src/superlocalmemory/hooks/hook_daemon.py +4 -2
  103. package/src/superlocalmemory/hooks/hook_handlers.py +241 -55
  104. package/src/superlocalmemory/hooks/memory_protocol.py +5 -3
  105. package/src/superlocalmemory/hooks/post_tool_async_hook.py +4 -2
  106. package/src/superlocalmemory/hooks/session_registry.py +15 -8
  107. package/src/superlocalmemory/hooks/stop_outcome_hook.py +10 -6
  108. package/src/superlocalmemory/hooks/topic_shift_hook.py +42 -12
  109. package/src/superlocalmemory/hooks/user_prompt_hook.py +9 -14
  110. package/src/superlocalmemory/hooks/user_prompt_rehash_hook.py +19 -11
  111. package/src/superlocalmemory/infra/auth_middleware.py +38 -5
  112. package/src/superlocalmemory/infra/backup.py +7 -5
  113. package/src/superlocalmemory/infra/cloud_backup.py +18 -8
  114. package/src/superlocalmemory/infra/daemon_identity.py +248 -0
  115. package/src/superlocalmemory/infra/data_root.py +199 -0
  116. package/src/superlocalmemory/infra/event_bus.py +3 -1
  117. package/src/superlocalmemory/infra/local_diagnostics.py +327 -0
  118. package/src/superlocalmemory/infra/process_reaper.py +23 -0
  119. package/src/superlocalmemory/ingestion/adapter_manager.py +27 -9
  120. package/src/superlocalmemory/ingestion/base_adapter.py +25 -31
  121. package/src/superlocalmemory/ingestion/calendar_adapter.py +13 -4
  122. package/src/superlocalmemory/ingestion/credentials.py +14 -7
  123. package/src/superlocalmemory/ingestion/gmail_adapter.py +13 -4
  124. package/src/superlocalmemory/ingestion/transcript_adapter.py +7 -2
  125. package/src/superlocalmemory/learning/consolidation_quantization_worker.py +1 -1
  126. package/src/superlocalmemory/learning/ensemble.py +11 -0
  127. package/src/superlocalmemory/learning/entity_compiler.py +1 -1
  128. package/src/superlocalmemory/learning/feedback.py +1 -1
  129. package/src/superlocalmemory/learning/forgetting_scheduler.py +12 -7
  130. package/src/superlocalmemory/learning/quantization_scheduler.py +1 -1
  131. package/src/superlocalmemory/learning/ranker.py +4 -1
  132. package/src/superlocalmemory/learning/source_quality.py +1 -1
  133. package/src/superlocalmemory/learning/trigram_index.py +3 -2
  134. package/src/superlocalmemory/llm/backbone.py +13 -8
  135. package/src/superlocalmemory/math/ebbinghaus.py +1 -1
  136. package/src/superlocalmemory/math/fisher.py +1 -1
  137. package/src/superlocalmemory/math/fisher_quantized.py +1 -1
  138. package/src/superlocalmemory/math/hopfield.py +1 -1
  139. package/src/superlocalmemory/math/langevin.py +1 -1
  140. package/src/superlocalmemory/math/polar_quant.py +3 -4
  141. package/src/superlocalmemory/math/qjl.py +1 -1
  142. package/src/superlocalmemory/math/sheaf.py +1 -1
  143. package/src/superlocalmemory/math/turbo_quant.py +3 -2
  144. package/src/superlocalmemory/mcp/_daemon_proxy.py +12 -11
  145. package/src/superlocalmemory/mcp/_pool_adapter.py +27 -0
  146. package/src/superlocalmemory/mcp/http_transport.py +53 -0
  147. package/src/superlocalmemory/mcp/server.py +39 -13
  148. package/src/superlocalmemory/mcp/shared.py +69 -3
  149. package/src/superlocalmemory/mcp/tools_active.py +141 -31
  150. package/src/superlocalmemory/mcp/tools_core.py +128 -29
  151. package/src/superlocalmemory/mcp/tools_evolution.py +5 -7
  152. package/src/superlocalmemory/mcp/tools_learning.py +42 -2
  153. package/src/superlocalmemory/mcp/tools_mesh.py +7 -23
  154. package/src/superlocalmemory/mcp/tools_optimize.py +8 -1
  155. package/src/superlocalmemory/mcp/tools_v28.py +23 -2
  156. package/src/superlocalmemory/mcp/tools_v3.py +26 -1
  157. package/src/superlocalmemory/mcp/tools_v33.py +56 -17
  158. package/src/superlocalmemory/mesh/broker.py +2 -0
  159. package/src/superlocalmemory/mesh/remote_sync.py +50 -12
  160. package/src/superlocalmemory/optimize/cache/manager.py +77 -1
  161. package/src/superlocalmemory/optimize/cache/semantic.py +23 -3
  162. package/src/superlocalmemory/optimize/compress/ccr.py +4 -0
  163. package/src/superlocalmemory/optimize/compress/router.py +6 -1
  164. package/src/superlocalmemory/optimize/config/__init__.py +5 -0
  165. package/src/superlocalmemory/optimize/config/store.py +6 -4
  166. package/src/superlocalmemory/optimize/proxy/_helpers.py +15 -5
  167. package/src/superlocalmemory/optimize/proxy/capture.py +3 -2
  168. package/src/superlocalmemory/optimize/proxy/server.py +2 -2
  169. package/src/superlocalmemory/optimize/storage/db.py +14 -13
  170. package/src/superlocalmemory/retrieval/agentic.py +1 -1
  171. package/src/superlocalmemory/retrieval/ann_index.py +1 -1
  172. package/src/superlocalmemory/retrieval/bm25_channel.py +35 -11
  173. package/src/superlocalmemory/retrieval/bridge_discovery.py +73 -8
  174. package/src/superlocalmemory/retrieval/engine.py +169 -79
  175. package/src/superlocalmemory/retrieval/entity_channel.py +289 -67
  176. package/src/superlocalmemory/retrieval/forgetting_filter.py +1 -1
  177. package/src/superlocalmemory/retrieval/fusion.py +1 -1
  178. package/src/superlocalmemory/retrieval/hopfield_channel.py +118 -30
  179. package/src/superlocalmemory/retrieval/profile_channel.py +1 -1
  180. package/src/superlocalmemory/retrieval/quantization_aware_search.py +16 -10
  181. package/src/superlocalmemory/retrieval/reranker.py +56 -20
  182. package/src/superlocalmemory/retrieval/scope_policy.py +85 -0
  183. package/src/superlocalmemory/retrieval/semantic_channel.py +122 -14
  184. package/src/superlocalmemory/retrieval/spreading_activation.py +141 -25
  185. package/src/superlocalmemory/retrieval/strategy.py +1 -1
  186. package/src/superlocalmemory/retrieval/temporal_channel.py +30 -15
  187. package/src/superlocalmemory/retrieval/vector_store.py +1 -1
  188. package/src/superlocalmemory/server/api.py +10 -7
  189. package/src/superlocalmemory/server/bandit_loops.py +4 -2
  190. package/src/superlocalmemory/server/recall_serializer.py +24 -0
  191. package/src/superlocalmemory/server/route_mutations.py +84 -0
  192. package/src/superlocalmemory/server/routes/agents.py +8 -6
  193. package/src/superlocalmemory/server/routes/brain.py +14 -12
  194. package/src/superlocalmemory/server/routes/chat.py +29 -12
  195. package/src/superlocalmemory/server/routes/data_io.py +55 -24
  196. package/src/superlocalmemory/server/routes/helpers.py +29 -4
  197. package/src/superlocalmemory/server/routes/ingest.py +53 -36
  198. package/src/superlocalmemory/server/routes/memories.py +104 -43
  199. package/src/superlocalmemory/server/routes/mesh.py +31 -0
  200. package/src/superlocalmemory/server/routes/profiles.py +26 -4
  201. package/src/superlocalmemory/server/routes/tiers.py +43 -11
  202. package/src/superlocalmemory/server/routes/timeline.py +5 -1
  203. package/src/superlocalmemory/server/routes/v3_api.py +76 -21
  204. package/src/superlocalmemory/server/security_middleware.py +1 -1
  205. package/src/superlocalmemory/server/ui.py +6 -3
  206. package/src/superlocalmemory/server/unified_daemon.py +680 -293
  207. package/src/superlocalmemory/server/write_identity.py +147 -0
  208. package/src/superlocalmemory/storage/access_log.py +4 -3
  209. package/src/superlocalmemory/storage/database.py +118 -25
  210. package/src/superlocalmemory/storage/migration_runner.py +84 -1
  211. package/src/superlocalmemory/storage/migration_v33.py +1 -1
  212. package/src/superlocalmemory/storage/migrations/M002_model_state_history.py +6 -60
  213. package/src/superlocalmemory/storage/migrations/M018_ingestion_operations.py +120 -0
  214. package/src/superlocalmemory/storage/migrations/M019_derivation_lineage.py +54 -0
  215. package/src/superlocalmemory/storage/migrations/M020_model_state_integrity.py +52 -0
  216. package/src/superlocalmemory/storage/migrations/__init__.py +5 -0
  217. package/src/superlocalmemory/storage/models.py +16 -0
  218. package/src/superlocalmemory/storage/quantized_store.py +20 -3
  219. package/src/superlocalmemory/storage/v2_migrator.py +5 -3
  220. package/src/superlocalmemory/ui/favicon.svg +5 -0
  221. package/src/superlocalmemory/ui/index.html +1 -0
  222. package/src/superlocalmemory/ui/js/compliance.js +1 -1
  223. package/src/superlocalmemory/ui/js/core.js +49 -8
  224. package/src/superlocalmemory/ui/js/dashboard.js +23 -2
  225. package/src/superlocalmemory/ui/js/feedback.js +1 -1
  226. package/src/superlocalmemory/ui/js/graph-filters.js +1 -1
  227. package/src/superlocalmemory/ui/js/graph-ui.js +1 -1
  228. package/src/superlocalmemory/ui/js/lifecycle.js +1 -1
  229. package/src/superlocalmemory/ui/js/ng-mesh.js +15 -49
  230. package/src/superlocalmemory/ui/js/settings.js +4 -2
  231. package/src/superlocalmemory/vector/lancedb_backend.py +57 -9
  232. package/bin/slm +0 -59
  233. package/bin/slm.bat +0 -77
  234. package/bin/slm.cmd +0 -5
  235. package/ide/integrations/langchain/README.md +0 -106
  236. package/ide/integrations/langchain/langchain_superlocalmemory/__init__.py +0 -9
  237. package/ide/integrations/langchain/langchain_superlocalmemory/chat_message_history.py +0 -201
  238. package/ide/integrations/langchain/pyproject.toml +0 -38
  239. package/ide/integrations/langchain/tests/__init__.py +0 -3
  240. package/ide/integrations/langchain/tests/test_chat_message_history.py +0 -215
  241. package/ide/integrations/langchain/tests/test_security.py +0 -117
  242. package/ide/integrations/llamaindex/README.md +0 -81
  243. package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/__init__.py +0 -9
  244. package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/base.py +0 -316
  245. package/ide/integrations/llamaindex/pyproject.toml +0 -43
  246. package/ide/integrations/llamaindex/tests/__init__.py +0 -3
  247. package/ide/integrations/llamaindex/tests/test_chat_store.py +0 -294
  248. package/ide/integrations/llamaindex/tests/test_security.py +0 -241
  249. package/plugin-src/.mcp.json +0 -12
  250. package/plugin-src/agents/slm-memory-advisor.md +0 -44
  251. package/plugin-src/agents/slm-optimize-advisor.md +0 -38
  252. package/plugin-src/hooks/.gitkeep +0 -0
  253. package/plugin-src/hooks/hooks.json +0 -23
  254. package/plugin-src/manifest.json +0 -25
  255. package/plugin-src/requirements.txt +0 -1
  256. package/plugin-src/rules/CLAUDE.md.fragment +0 -44
  257. package/plugin-src/scripts/ensure-venv.bat +0 -122
  258. package/plugin-src/scripts/ensure-venv.sh +0 -105
  259. package/plugin-src/scripts/slm-launch +0 -15
  260. package/plugin-src/scripts/slm-launch.bat +0 -17
  261. package/plugin-src/settings.json +0 -16
  262. package/plugin-src/skills/slm-cache/SKILL.md +0 -140
  263. package/plugin-src/skills/slm-compress/SKILL.md +0 -143
  264. package/plugin-src/skills/slm-graph/SKILL.md +0 -300
  265. package/plugin-src/skills/slm-recall/SKILL.md +0 -204
  266. package/plugin-src/skills/slm-remember/SKILL.md +0 -194
  267. package/plugin-src/skills/slm-session/SKILL.md +0 -207
  268. package/plugin-src/skills/slm-status/SKILL.md +0 -149
  269. package/scripts/__tests__/build-plugin.test.mjs +0 -613
  270. package/scripts/_savings_math.py +0 -270
  271. package/scripts/build-dmg.sh +0 -417
  272. package/scripts/build-plugin.js +0 -742
  273. package/scripts/build-slm-hook.ps1 +0 -40
  274. package/scripts/build-slm-hook.sh +0 -45
  275. package/scripts/build_entry.py +0 -452
  276. package/scripts/ci/stage5b_gate.sh +0 -50
  277. package/scripts/dogfood_savings.py +0 -490
  278. package/scripts/generate-thumbnails.py +0 -218
  279. package/scripts/install-skills.ps1 +0 -4
  280. package/scripts/install-skills.sh +0 -5
  281. package/scripts/install.ps1 +0 -701
  282. package/scripts/install.sh +0 -1015
  283. package/scripts/postinstall_binary.js +0 -287
  284. package/scripts/prepack.js +0 -33
  285. package/scripts/release_manifest.py +0 -273
  286. package/scripts/slm-hook.spec +0 -56
  287. package/scripts/start-dashboard.ps1 +0 -52
  288. package/scripts/start-dashboard.sh +0 -41
  289. package/scripts/sync-wiki.ps1 +0 -127
  290. package/scripts/sync-wiki.sh +0 -82
  291. package/scripts/test-dmg.sh +0 -161
  292. package/scripts/test-npm-package.ps1 +0 -252
  293. package/scripts/test-npm-package.sh +0 -207
  294. package/scripts/verify-install.ps1 +0 -294
  295. package/scripts/verify-install.sh +0 -266
  296. package/scripts/verify-v27.ps1 +0 -301
  297. package/scripts/verify-v27.sh +0 -233
  298. package/src/superlocalmemory.egg-info/PKG-INFO +0 -513
  299. package/src/superlocalmemory.egg-info/SOURCES.txt +0 -529
  300. package/src/superlocalmemory.egg-info/dependency_links.txt +0 -1
  301. package/src/superlocalmemory.egg-info/entry_points.txt +0 -2
  302. package/src/superlocalmemory.egg-info/requires.txt +0 -71
  303. package/src/superlocalmemory.egg-info/top_level.txt +0 -1
@@ -0,0 +1,293 @@
1
+ # Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
2
+ # Licensed under AGPL-3.0-or-later - see LICENSE file
3
+
4
+ """Staged lifecycle for optional CozoDB and LanceDB projections.
5
+
6
+ SQLite and sqlite-vec remain the canonical store. The Scale Engine is a
7
+ derived projection: it is prepared outside the active paths, checked against
8
+ the canonical store, and only then promoted. This prevents an installed
9
+ optional dependency from mutating an existing user's data root at daemon
10
+ startup.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import hashlib
16
+ import json
17
+ import os
18
+ import shutil
19
+ import sqlite3
20
+ import uuid
21
+ from datetime import UTC, datetime
22
+ from pathlib import Path
23
+ from typing import Any, Callable
24
+
25
+
26
+ class ScaleEngineError(RuntimeError):
27
+ """A scale projection cannot safely advance to its next lifecycle state."""
28
+
29
+
30
+ def _utc_now() -> str:
31
+ return datetime.now(UTC).isoformat()
32
+
33
+
34
+ class ScaleEngineManager:
35
+ """Prepare, verify, promote, and roll back derived scale projections.
36
+
37
+ ``backend_factory`` is intentionally injectable. It keeps lifecycle tests
38
+ independent from native optional packages while production uses the real
39
+ CozoDB and LanceDB wrappers.
40
+ """
41
+
42
+ MANIFEST_NAME = "scale-engine.json"
43
+ SCHEMA_VERSION = 1
44
+
45
+ def __init__(
46
+ self,
47
+ config: Any,
48
+ *,
49
+ backend_factory: Callable[[Path, Path], tuple[Any, Any]] | None = None,
50
+ ) -> None:
51
+ self.config = config
52
+ self.data_dir = Path(getattr(config, "data_dir", None) or config.base_dir)
53
+ self.db_path = Path(getattr(config, "db_path", None) or self.data_dir / "memory.db")
54
+ self.profile_id = getattr(config, "active_profile", "default")
55
+ self._backend_factory = backend_factory or self._real_backend_factory
56
+
57
+ @property
58
+ def staging_root(self) -> Path:
59
+ return self.data_dir / "scale-staging"
60
+
61
+ @property
62
+ def backup_root(self) -> Path:
63
+ return self.data_dir / "scale-backups"
64
+
65
+ @property
66
+ def active_paths(self) -> tuple[Path, Path]:
67
+ return self.data_dir / "cozo", self.data_dir / "lance"
68
+
69
+ def status(self) -> dict[str, Any]:
70
+ manifests: list[dict[str, Any]] = []
71
+ if self.staging_root.exists():
72
+ for path in sorted(self.staging_root.glob(f"*/{self.MANIFEST_NAME}")):
73
+ try:
74
+ manifests.append(json.loads(path.read_text()))
75
+ except (OSError, json.JSONDecodeError):
76
+ manifests.append({"stage_id": path.parent.name, "state": "corrupt"})
77
+ return {
78
+ "state": getattr(self.config, "scale_engine_state", "local_core"),
79
+ "active": {"cozo": self.active_paths[0].exists(), "lance": self.active_paths[1].exists()},
80
+ "stages": manifests,
81
+ "backups": sorted(p.name for p in self.backup_root.glob("*") if p.is_dir()) if self.backup_root.exists() else [],
82
+ }
83
+
84
+ def prepare(self) -> dict[str, Any]:
85
+ """Build a new projection in a private staging directory."""
86
+ self._require_default_profile()
87
+ self._require_canonical_db()
88
+ stage_id = f"{datetime.now(UTC).strftime('%Y%m%dT%H%M%SZ')}-{uuid.uuid4().hex[:8]}"
89
+ stage_dir = self.staging_root / stage_id
90
+ cozo_dir, lance_dir = stage_dir / "cozo", stage_dir / "lance"
91
+ stage_dir.mkdir(parents=True, exist_ok=False)
92
+ cozo = lance = None
93
+ try:
94
+ cozo, lance = self._backend_factory(cozo_dir, lance_dir)
95
+ with self._readonly_connection() as conn:
96
+ cozo.bulk_import_from_sqlite(conn, self.profile_id)
97
+ lance.bulk_import_from_sqlite(conn, self.profile_id)
98
+ canonical = self._canonical_counts(conn)
99
+ observed = self._observed_counts(cozo, lance)
100
+ manifest = {
101
+ "schema_version": self.SCHEMA_VERSION,
102
+ "stage_id": stage_id,
103
+ "state": "prepared",
104
+ "created_at": _utc_now(),
105
+ "profile_id": self.profile_id,
106
+ "canonical": canonical,
107
+ "observed": observed,
108
+ "source_fingerprint": self._source_fingerprint(canonical),
109
+ }
110
+ self._write_manifest(stage_dir, manifest)
111
+ self.config.scale_engine_state = "prepared"
112
+ self._save_config()
113
+ return manifest
114
+ except Exception:
115
+ shutil.rmtree(stage_dir, ignore_errors=True)
116
+ raise
117
+ finally:
118
+ self._close(cozo)
119
+ self._close(lance)
120
+
121
+ def verify(self, stage_id: str) -> dict[str, Any]:
122
+ """Prove a staged projection matches the current canonical SQLite data."""
123
+ stage_dir, manifest = self._load_stage(stage_id)
124
+ self._validate_manifest(manifest, state="prepared")
125
+ self._require_default_profile()
126
+ cozo = lance = None
127
+ try:
128
+ cozo, lance = self._backend_factory(stage_dir / "cozo", stage_dir / "lance")
129
+ with self._readonly_connection() as conn:
130
+ canonical = self._canonical_counts(conn)
131
+ observed = self._observed_counts(cozo, lance)
132
+ if manifest["source_fingerprint"] != self._source_fingerprint(canonical):
133
+ raise ScaleEngineError("canonical SQLite changed after preparation; prepare a new stage")
134
+ if canonical != manifest["canonical"] or observed != canonical:
135
+ raise ScaleEngineError(
136
+ f"projection parity failed: canonical={canonical}, observed={observed}"
137
+ )
138
+ manifest.update({"state": "verified", "verified_at": _utc_now(), "observed": observed})
139
+ self._write_manifest(stage_dir, manifest)
140
+ self.config.scale_engine_state = "verified"
141
+ self._save_config()
142
+ return manifest
143
+ finally:
144
+ self._close(cozo)
145
+ self._close(lance)
146
+
147
+ def promote(self, stage_id: str) -> dict[str, Any]:
148
+ """Move a verified stage into active paths, preserving a rollback copy."""
149
+ stage_dir, manifest = self._load_stage(stage_id)
150
+ self._validate_manifest(manifest, state="verified")
151
+ staged = (stage_dir / "cozo", stage_dir / "lance")
152
+ if not all(path.exists() for path in staged):
153
+ raise ScaleEngineError("verified stage is incomplete; prepare a new stage")
154
+ backup_dir = self.backup_root / f"{stage_id}-{uuid.uuid4().hex[:6]}"
155
+ active = self.active_paths
156
+ self.backup_root.mkdir(parents=True, exist_ok=True)
157
+ # Keep an explicit empty rollback point as well: a first promotion has
158
+ # no former projection directories, but rollback must still be able to
159
+ # return the installation to Local Core without deleting anything.
160
+ backup_dir.mkdir(parents=True, exist_ok=False)
161
+ moved_active: list[tuple[Path, Path]] = []
162
+ moved_stage: list[tuple[Path, Path]] = []
163
+ try:
164
+ for name, source, destination in zip(("cozo", "lance"), active, staged):
165
+ if source.exists():
166
+ target = backup_dir / name
167
+ os.replace(source, target)
168
+ moved_active.append((source, target))
169
+ os.replace(destination, source)
170
+ moved_stage.append((destination, source))
171
+ manifest.update({"state": "promoted", "promoted_at": _utc_now(), "backup_id": backup_dir.name})
172
+ self._write_manifest(stage_dir, manifest)
173
+ self.config.scale_engine_state = "promoted"
174
+ self.config.graph_backend = "cozo"
175
+ self.config.vector_backend = "lancedb"
176
+ self._save_config()
177
+ return manifest
178
+ except Exception as exc:
179
+ for staged_path, active_path in reversed(moved_stage):
180
+ if active_path.exists():
181
+ os.replace(active_path, staged_path)
182
+ for active_path, backup_path in reversed(moved_active):
183
+ if backup_path.exists():
184
+ os.replace(backup_path, active_path)
185
+ raise ScaleEngineError(f"promotion rolled back: {exc}") from exc
186
+
187
+ def rollback(self, backup_id: str) -> dict[str, Any]:
188
+ """Restore an explicitly named pre-promotion backup."""
189
+ backup_dir = self.backup_root / backup_id
190
+ if not backup_dir.is_dir():
191
+ raise ScaleEngineError(f"backup does not exist: {backup_id}")
192
+ backup_paths = (backup_dir / "cozo", backup_dir / "lance")
193
+ active = self.active_paths
194
+ displaced = self.backup_root / f"rollback-displaced-{uuid.uuid4().hex[:8]}"
195
+ try:
196
+ for name, source, target in zip(("cozo", "lance"), active, backup_paths):
197
+ if source.exists():
198
+ displaced.mkdir(parents=True, exist_ok=True)
199
+ os.replace(source, displaced / name)
200
+ if target.exists():
201
+ os.replace(target, source)
202
+ self.config.scale_engine_state = "local_core"
203
+ self.config.graph_backend = "auto"
204
+ self.config.vector_backend = "auto"
205
+ self._save_config()
206
+ return {"state": "local_core", "restored_backup": backup_id, "displaced": displaced.name}
207
+ except Exception as exc:
208
+ raise ScaleEngineError(f"rollback failed; inspect {backup_dir}: {exc}") from exc
209
+
210
+ def _real_backend_factory(self, cozo_dir: Path, lance_dir: Path) -> tuple[Any, Any]:
211
+ from superlocalmemory.graph.cozo_backend import CozoDBGraphBackend
212
+ from superlocalmemory.vector.lancedb_backend import LanceDBVectorBackend
213
+ return CozoDBGraphBackend(str(cozo_dir / "graph")), LanceDBVectorBackend(str(lance_dir))
214
+
215
+ def _readonly_connection(self) -> sqlite3.Connection:
216
+ conn = sqlite3.connect(f"file:{self.db_path}?mode=ro", uri=True)
217
+ conn.execute("PRAGMA query_only=ON")
218
+ return conn
219
+
220
+ def _canonical_counts(self, conn: sqlite3.Connection) -> dict[str, int]:
221
+ nodes = conn.execute(
222
+ "SELECT COUNT(*) FROM canonical_entities WHERE profile_id=?",
223
+ (self.profile_id,),
224
+ ).fetchone()[0]
225
+ edges = conn.execute(
226
+ "SELECT COUNT(*) FROM graph_edges WHERE profile_id=?", (self.profile_id,)
227
+ ).fetchone()[0]
228
+ try:
229
+ vectors = conn.execute(
230
+ "SELECT COUNT(*) FROM fact_embeddings_rowids fer "
231
+ "JOIN atomic_facts af ON af.fact_id = fer.fact_id "
232
+ "WHERE af.profile_id = ?",
233
+ (self.profile_id,),
234
+ ).fetchone()[0]
235
+ except sqlite3.OperationalError:
236
+ vectors = 0
237
+ return {"entities": int(nodes), "edges": int(edges), "vectors": int(vectors)}
238
+
239
+ def _observed_counts(self, cozo: Any, lance: Any) -> dict[str, int]:
240
+ graph = cozo.health_check()
241
+ vector = lance.health_check()
242
+ if graph.get("status") != "active" or vector.get("status") != "active":
243
+ raise ScaleEngineError(f"projection health failed: cozo={graph}, lancedb={vector}")
244
+ return {"entities": int(graph["entities"]), "edges": int(graph["edges"]), "vectors": int(vector["vectors"])}
245
+
246
+ def _source_fingerprint(self, counts: dict[str, int]) -> str:
247
+ digest = hashlib.sha256()
248
+ digest.update(json.dumps(counts, sort_keys=True).encode())
249
+ for path in (self.db_path, self.db_path.with_name(self.db_path.name + "-wal")):
250
+ if path.exists():
251
+ digest.update(path.name.encode())
252
+ with path.open("rb") as handle:
253
+ for block in iter(lambda: handle.read(1024 * 1024), b""):
254
+ digest.update(block)
255
+ return digest.hexdigest()
256
+
257
+ def _load_stage(self, stage_id: str) -> tuple[Path, dict[str, Any]]:
258
+ stage_dir = self.staging_root / stage_id
259
+ try:
260
+ manifest = json.loads((stage_dir / self.MANIFEST_NAME).read_text())
261
+ except (OSError, json.JSONDecodeError) as exc:
262
+ raise ScaleEngineError(f"invalid scale stage: {stage_id}") from exc
263
+ return stage_dir, manifest
264
+
265
+ def _write_manifest(self, stage_dir: Path, manifest: dict[str, Any]) -> None:
266
+ target = stage_dir / self.MANIFEST_NAME
267
+ temporary = target.with_suffix(".tmp")
268
+ temporary.write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n")
269
+ os.replace(temporary, target)
270
+
271
+ def _validate_manifest(self, manifest: dict[str, Any], *, state: str) -> None:
272
+ if manifest.get("schema_version") != self.SCHEMA_VERSION or manifest.get("state") != state:
273
+ raise ScaleEngineError(f"stage must be {state}, got {manifest.get('state')!r}")
274
+
275
+ def _require_default_profile(self) -> None:
276
+ if self.profile_id != "default":
277
+ raise ScaleEngineError("Scale Engine promotion currently supports the default profile only")
278
+
279
+ def _require_canonical_db(self) -> None:
280
+ if not self.db_path.exists():
281
+ raise ScaleEngineError(f"canonical SQLite database not found: {self.db_path}")
282
+
283
+ def _save_config(self) -> None:
284
+ save = getattr(self.config, "save", None)
285
+ if callable(save):
286
+ save()
287
+
288
+ @staticmethod
289
+ def _close(resource: Any) -> None:
290
+ if resource is not None:
291
+ close = getattr(resource, "close", None)
292
+ if callable(close):
293
+ close()
@@ -0,0 +1,62 @@
1
+ # Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
2
+ # Licensed under AGPL-3.0-or-later - see LICENSE file
3
+
4
+ """Truthful public retrieval score contract.
5
+
6
+ Retrieval produces an ordering, not an answer probability. This module keeps
7
+ query relevance, stored assertion confidence, trust, and internal ranking
8
+ utility distinct while preserving the V3.6 aliases for one compatibility
9
+ release.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import math
15
+
16
+ from superlocalmemory.storage.models import RecallResponse
17
+
18
+
19
+ def _bounded(value: object, default: float = 0.0) -> float:
20
+ try:
21
+ number = float(value)
22
+ except (TypeError, ValueError):
23
+ number = default
24
+ if not math.isfinite(number):
25
+ number = default
26
+ return min(1.0, max(0.0, number))
27
+
28
+
29
+ def finalize_score_contract(response: RecallResponse) -> RecallResponse:
30
+ """Finalize aliases, rank positions, and response abstention metadata."""
31
+ for position, result in enumerate(response.results or (), start=1):
32
+ relevance = _bounded(
33
+ result.relevance_score
34
+ if result.relevance_score is not None
35
+ else result.score
36
+ )
37
+ memory_confidence = _bounded(
38
+ result.memory_confidence
39
+ if result.memory_confidence is not None
40
+ else getattr(result.fact, "confidence", 0.0)
41
+ )
42
+ result.relevance_score = relevance
43
+ result.score = relevance
44
+ result.memory_confidence = memory_confidence
45
+ result.confidence = memory_confidence
46
+ result.rank_position = position
47
+
48
+ response.score_contract_version = "2"
49
+ response.calibration_status = "uncalibrated"
50
+ response.calibration_id = None
51
+ response.answer_confidence = None
52
+ response.abstained = not bool(response.results)
53
+ if response.abstained:
54
+ response.abstention_reason = (
55
+ "evidence_floor" if response.no_confident_match else "no_candidates"
56
+ )
57
+ else:
58
+ response.abstention_reason = None
59
+ return response
60
+
61
+
62
+ __all__ = ["finalize_score_contract"]
@@ -401,7 +401,9 @@ def redact_secrets(text: str, *, entropy_threshold: float = 4.5,
401
401
 
402
402
  def _install_token_path() -> Path: # pragma: no cover — monkeypatched in tests
403
403
  """Default install-token location — override in tests via monkeypatch."""
404
- return Path.home() / ".superlocalmemory" / ".install_token"
404
+ from superlocalmemory.infra.data_root import state_path
405
+
406
+ return state_path(".install_token")
405
407
 
406
408
 
407
409
  def ensure_install_token() -> str:
@@ -30,11 +30,9 @@ _ENV_NAME = "SLM_DISABLE"
30
30
 
31
31
 
32
32
  def _slm_home() -> Path:
33
- """Return the SLM state directory. Override via ``SLM_HOME`` env."""
34
- override = os.environ.get("SLM_HOME")
35
- if override:
36
- return Path(override)
37
- return Path.home() / ".superlocalmemory"
33
+ """Return the canonical SLM runtime-state directory."""
34
+ from superlocalmemory.infra.data_root import canonical_data_root
35
+ return canonical_data_root()
38
36
 
39
37
 
40
38
  def marker_path() -> Path: