superlocalmemory 4.0.5 → 4.0.6

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 (42) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/README.md +10 -6
  3. package/package.json +3 -1
  4. package/pyproject.toml +1 -1
  5. package/src/superlocalmemory/__init__.py +1 -1
  6. package/src/superlocalmemory/access/rbac.py +106 -0
  7. package/src/superlocalmemory/brain/truth.py +80 -10
  8. package/src/superlocalmemory/cli/__main__.py +17 -0
  9. package/src/superlocalmemory/cli/commands.py +14 -3
  10. package/src/superlocalmemory/cli/gdpr_cmd.py +779 -0
  11. package/src/superlocalmemory/cli/gdpr_io.py +109 -0
  12. package/src/superlocalmemory/cli/main.py +76 -0
  13. package/src/superlocalmemory/code_graph/extractors/__init__.py +17 -0
  14. package/src/superlocalmemory/code_graph/graph_store.py +180 -3
  15. package/src/superlocalmemory/code_graph/parser.py +280 -100
  16. package/src/superlocalmemory/compliance/gdpr.py +358 -0
  17. package/src/superlocalmemory/core/config.py +44 -1
  18. package/src/superlocalmemory/core/engine_wiring.py +5 -1
  19. package/src/superlocalmemory/core/maintenance.py +43 -1
  20. package/src/superlocalmemory/core/recall_worker.py +33 -12
  21. package/src/superlocalmemory/infra/backup.py +138 -0
  22. package/src/superlocalmemory/infra/backup_obligations.py +423 -0
  23. package/src/superlocalmemory/learning/engagement.py +165 -0
  24. package/src/superlocalmemory/mcp/tools_code_graph.py +31 -4
  25. package/src/superlocalmemory/mcp/tools_v3.py +20 -6
  26. package/src/superlocalmemory/retrieval/engine.py +21 -0
  27. package/src/superlocalmemory/retrieval/remote_reranker.py +108 -11
  28. package/src/superlocalmemory/server/routes/brain.py +283 -15
  29. package/src/superlocalmemory/server/routes/learning.py +13 -25
  30. package/src/superlocalmemory/server/routes/v3_api.py +171 -60
  31. package/src/superlocalmemory/storage/database.py +36 -0
  32. package/src/superlocalmemory/storage/models.py +12 -4
  33. package/src/superlocalmemory/summaries/__init__.py +37 -0
  34. package/src/superlocalmemory/summaries/base.py +108 -0
  35. package/src/superlocalmemory/summaries/daily_reflection.py +293 -0
  36. package/src/superlocalmemory/summaries/project_work_log.py +424 -0
  37. package/src/superlocalmemory/summaries/session_summary.py +307 -0
  38. package/src/superlocalmemory/ui/css/design-system.css +76 -1
  39. package/src/superlocalmemory/ui/index.html +28 -11
  40. package/src/superlocalmemory/ui/js/od-agents.js +49 -5
  41. package/src/superlocalmemory/ui/js/od-brain.js +257 -77
  42. package/src/superlocalmemory/ui/js/od-graph.js +147 -6
package/CHANGELOG.md CHANGED
@@ -5,6 +5,63 @@ All notable changes to SuperLocalMemory will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [4.0.6] — The Connected Brain
9
+
10
+ ### Added
11
+ - **The Living Brain, rewritten for people who do not read telemetry.** The Brain
12
+ section now leads with the number of questions your memory has answered rather
13
+ than a raw event count, names its ranking phase in words, and stops presenting
14
+ a starting value as a measured result. Source quality no longer lists internal
15
+ identifiers; when nothing has been measured yet it says so and explains what
16
+ would change that.
17
+ - **Session, daily and project summaries.** A readable layer over your memories:
18
+ what a session covered, what a day's main topics were, and what was worked on
19
+ per project. Each one links back to the memories it came from, and each states
20
+ how much of the underlying data it could actually cover. Requested in #113.
21
+ - **Entity-level memory consolidation now runs.** Repeated facts about the same
22
+ entity are merged during maintenance, and the originals are archived rather
23
+ than deleted.
24
+ - **Codex and Bounded Loops appear in Connected clients.** Codex is listed with
25
+ the configuration that proves it, and Bounded Loops is detected when installed,
26
+ with its version and the bridge it speaks.
27
+
28
+ ### Fixed
29
+ - **The knowledge graph no longer opens blank.** Previously the graph could
30
+ render nothing on first open — and again when you returned to it — until you
31
+ moved the node slider. Two separate causes: the view framed itself against a
32
+ canvas that had no size yet, and re-entering the pane cleared the canvas
33
+ without redrawing it. Default node count is now 50.
34
+ - **The graph's details and chat panel is reachable on smaller screens.** Below
35
+ 1100px it stacks under the graph, a full screen-height out of view; there is
36
+ now a control to reach it and a way back.
37
+ - **Presence tells you when it has stopped being recorded.** A gap in recording
38
+ previously looked identical to "no agents are active".
39
+ - **Recall no longer fails when the reranker returns no scores.** It falls back
40
+ to its existing ranking instead of raising.
41
+ - Storage: per-call connections now disable checkpoint-on-close
42
+ (`SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE`), removing a version-dependent blocking
43
+ close path. On SQLite builds whose close-path checkpoint can block, closing a
44
+ WAL-mode connection may wait on reader marks pinned by another connection
45
+ while holding SQLite's process-global VFS mutex, convoying every later
46
+ connection in the process; `PRAGMA busy_timeout` does not apply to that path.
47
+ Reported, diagnosed and fixed by **@kenyonxu** (#118), from a production
48
+ postmortem plus an in-suite reproduction.
49
+ Scope note: on SQLite 3.49.1 the close-path checkpoint is passive — measured
50
+ at 0.075 ms with a full WAL and a pinned reader mark — so the convoy is not
51
+ reproducible there. This change is therefore hardening: it makes the
52
+ non-blocking close explicit rather than depending on the behaviour of a
53
+ particular SQLite build.
54
+ - Storage: per-call connections now also set `PRAGMA wal_autocheckpoint=400`.
55
+ This pragma is per-connection and is not persisted in the database file, so
56
+ it previously applied only to the short-lived initialisation connection and
57
+ every working connection silently fell back to SQLite's default of 1000
58
+ frames. With checkpoint-on-close disabled, autocheckpoint is the only
59
+ remaining checkpoint path, so the intended value must be set where the
60
+ writes actually happen.
61
+ - Storage: when checkpoint-on-close cannot be disabled (Python 3.11, which
62
+ predates `Connection.setconfig`), this is now logged once instead of failing
63
+ silently, so operators know the deadlock hardening is inactive.
64
+
8
65
  ## [4.0.5] - 2026-08-16 — Reviewed time-aware corrections
9
66
 
10
67
  ### Added
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  </picture>
6
6
  </p>
7
7
 
8
- <h1 align="center">SuperLocalMemory V4.0.5</h1>
8
+ <h1 align="center">SuperLocalMemory V4.0.6</h1>
9
9
 
10
10
  <h2 align="center">Rent the LLM. Own the memory.</h2>
11
11
 
@@ -27,12 +27,12 @@ guarantee here is stated as a falsifiable invariant, tested under an adversarial
27
27
  negative control, and shipped with the harness that regenerates the evidence:
28
28
  <code>python benchmark/run_all.py --trials 200 --output-dir results/</code>. What each experiment
29
29
  does <em>not</em> exercise is stated too.</p>
30
- <p align="center"><code>v4.0.5</code> — one control plane: <strong>SLM-Mesh</strong> peer coordination · multi-scope memory (personal / shared / global) · profiles · Cache · Compress · 7-layer retrieval · code graph · Entity Explorer · skill evolution · Modes A/B/C · GDPR retention &amp; audit chain · bounded loops — across CLI, MCP, dashboard, the <strong>Claude plugin</strong>, the <strong>Codex add-on</strong>, and documented IDE integrations.<br/>
30
+ <p align="center"><code>v4.0.6</code> — one control plane: <strong>SLM-Mesh</strong> peer coordination · multi-scope memory (personal / shared / global) · profiles · Cache · Compress · 7-layer retrieval · code graph · Entity Explorer · skill evolution · Modes A/B/C · GDPR retention &amp; audit chain · bounded loops — across CLI, MCP, dashboard, the <strong>Claude plugin</strong>, the <strong>Codex add-on</strong>, and documented IDE integrations.<br/>
31
31
  Proxy: <code>slm wrap claude</code> &nbsp;·&nbsp; MCP: add <code>slm_compress</code> to your config &nbsp;·&nbsp; Skill: zero-config</p>
32
32
  <p align="center"><strong>Four public arXiv preprints</strong> · V4: <a href="https://arxiv.org/abs/2608.08253">arXiv:2608.08253</a> · companion archive: <a href="https://zenodo.org/records/21853302">Zenodo 21853302</a> (<a href="https://doi.org/10.5281/zenodo.21853302">DOI 10.5281/zenodo.21853302</a>) · prior preprints: <a href="https://arxiv.org/abs/2603.02240">2603.02240</a> · <a href="https://arxiv.org/abs/2603.14588">2603.14588</a> · <a href="https://arxiv.org/abs/2604.04514">2604.04514</a>.</p>
33
33
 
34
34
  <p align="center">
35
- <a href="CHANGELOG.md"><img src="https://img.shields.io/badge/v4.0.5-Current_Release-2ea44f?style=for-the-badge&logo=checkmarx&logoColor=white" alt="v4.0.5 — Current Release"/></a>
35
+ <a href="CHANGELOG.md"><img src="https://img.shields.io/badge/v4.0.6-Current_Release-2ea44f?style=for-the-badge&logo=checkmarx&logoColor=white" alt="v4.0.6 — Current Release"/></a>
36
36
  <a href="https://arxiv.org/abs/2608.08253"><img src="https://img.shields.io/badge/arXiv-2608.08253-b31b1b?style=for-the-badge&logo=arxiv&logoColor=white" alt="SuperLocalMemory 4.0 paper on arXiv:2608.08253"/></a>
37
37
  <a href="https://zenodo.org/records/21853302"><img src="https://img.shields.io/badge/Zenodo-10.5281%2Fzenodo.21853302-1682D4?style=for-the-badge&logo=zenodo&logoColor=white" alt="V4 paper on Zenodo: 10.5281/zenodo.21853302"/></a>
38
38
  <a href="https://arxiv.org/abs/2603.14588"><img src="https://img.shields.io/badge/arXiv-2603.14588-b31b1b?style=for-the-badge&logo=arxiv&logoColor=white" alt="arXiv Paper"/></a>
@@ -41,7 +41,7 @@ Proxy: <code>slm wrap claude</code> &nbsp;·&nbsp; MCP: add <code>slm_compress</
41
41
  <a href="https://www.npmjs.com/package/superlocalmemory"><img src="https://img.shields.io/npm/v/superlocalmemory?style=for-the-badge&logo=npm&logoColor=white" alt="npm"/></a>
42
42
  <a href="https://www.gnu.org/licenses/agpl-3.0"><img src="https://img.shields.io/badge/License-AGPL_v3-blue.svg?style=for-the-badge" alt="AGPL v3"/></a>
43
43
  <a href="#privacy-controls-and-operating-modes"><img src="https://img.shields.io/badge/Privacy-Deployment_Assessed-brightgreen?style=for-the-badge" alt="Privacy controls require deployment assessment"/></a>
44
- <a href="#teams-and-enterprise-memory-v4"><img src="https://img.shields.io/badge/Enterprise-GDPR_%7C_EU_AI_Act-0b5394?style=for-the-badge" alt="Enterprise governance: GDPR and EU AI Act controls"/></a>
44
+ <a href="#teams-and-enterprise-memory-v4"><img src="https://img.shields.io/badge/Enterprise-GDPR_%7C_EU_AI_Act_controls-0b5394?style=for-the-badge" alt="Enterprise governance: GDPR and EU AI Act controls"/></a>
45
45
  <a href="https://superlocalmemory.com"><img src="https://img.shields.io/badge/Web-superlocalmemory.com-ff6b35?style=for-the-badge" alt="Website"/></a>
46
46
  <a href="#dual-interface-mcp--cli"><img src="https://img.shields.io/badge/MCP-Native-blue?style=for-the-badge" alt="MCP Native"/></a>
47
47
  <a href="#dual-interface-mcp--cli"><img src="https://img.shields.io/badge/CLI-Agent--Native-green?style=for-the-badge" alt="CLI Agent-Native"/></a>
@@ -62,9 +62,13 @@ SuperLocalMemory V4 combines conventional dense and lexical retrieval with graph
62
62
 
63
63
  **Memory with a sense of time.** SLM does not only store *what* an agent learned — it records *when*. Every fact carries ingestion timing and provenance; recall runs a dedicated temporal candidate channel alongside semantic, lexical, and associative retrieval; scenes and entity timelines reconstruct sequence; and the lifecycle lets neglected memory decay and self-archive instead of growing without bound. Time is a first-class ranking and lifecycle signal rather than a timestamp column an agent never reads — which is what lets a long-lived agent reason about how its context changed, not only what it currently holds.
64
64
 
65
- **What V4.0.5 ships.** A correction is now a review-gated lifecycle, not an in-place edit: SLM creates an immutable successor, keeps it out of current recall until an authenticated reviewer applies it, and preserves the predecessor for time-aware history. Every candidate path, including cached context, pins, bridge expansion, and scene expansion, uses hard current-truth admission and abstains if that truth cannot be read. `slm brain`, MCP, HTTP, and the Living Brain share one observation-only BrainTruth snapshot; feedback, external Bounded Loops evidence, and receipt claims are shown honestly but do not silently alter recall, ranking, or model routing. The optional Bounded Loops bridge remains capability-negotiated and independent. See [reviewed corrections](docs/reviewed-corrections.md) for the lifecycle and [MCP tools](docs/mcp-tools.md) for host-facing commands.
65
+ **What V4.0.6 ships.** The Living Brain is written for the person who owns the memory, not for the person who built the ranker. It leads with how many questions your memory has actually answered instead of a raw event count, names its ranking phase in words, and refuses to present an untrained starting value as a measured result — where nothing has been measured yet, it says so and says what would change that. Alongside it, a readable consolidation layer: session, daily, and project summaries, each linked back to the memories it was derived from and each explicit about how much of the underlying data it could cover. Entity-level consolidation now actually runs during maintenance, merging repeated facts about the same entity and archiving the originals rather than deleting them. Codex and Bounded Loops are detected and shown among connected clients, labelled by the evidence that supports them — configuration proves setup, not traffic, and the dashboard says which one it has. Presence now distinguishes "no agents are active" from "presence has stopped being recorded", because those looked identical before and only one of them is fine. See [reviewed corrections](docs/reviewed-corrections.md) for the correction lifecycle and [MCP tools](docs/mcp-tools.md) for host-facing commands.
66
66
 
67
- **Adaptive-ranking migration.** V4.0.5 leaves the optional adaptive ranker off
67
+ **Fixed in V4.0.6.** The knowledge graph no longer opens blank. It framed itself against a canvas that had no size yet, spent its whole frame budget drawing off-screen, and stopped — so the graph appeared only after you moved the node slider. Returning to the pane cleared the canvas without redrawing it, which failed the same way for a different reason. Both are fixed, the default node count is 50, and on narrower screens the details and chat panel is reachable instead of stranded a screen-height below the fold. Recall no longer raises when the reranker reports success but returns no scores; it falls back to its existing ranking. Corrections attempted without the daemon now refuse with the reason and the remedy, rather than reporting a transient error that could never clear.
68
+
69
+ **Carried forward from V4.0.5.** A correction is a review-gated lifecycle, not an in-place edit: SLM creates an immutable successor, keeps it out of current recall until an authenticated reviewer applies it, and preserves the predecessor for time-aware history. Every candidate path, including cached context, pins, bridge expansion, and scene expansion, uses hard current-truth admission and abstains if that truth cannot be read. `slm brain`, MCP, HTTP, and the Living Brain share one observation-only BrainTruth snapshot; feedback, external Bounded Loops evidence, and receipt claims are shown honestly but do not silently alter recall, ranking, or model routing. The optional Bounded Loops bridge remains capability-negotiated and independent.
70
+
71
+ **Adaptive-ranking migration.** V4.0.6 leaves the optional adaptive ranker off
68
72
  unless an operator sets `SLM_RANKING` (`v1`, `v2`, or `v2-ensemble`). This does
69
73
  not disable the normal retrieval channels; it prevents feedback and
70
74
  observation data from changing ranking without an explicit operator decision.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "superlocalmemory",
3
- "version": "4.0.5",
3
+ "version": "4.0.6",
4
4
  "description": "Local-first agent memory with MCP and an agent-native CLI. Documented clients include Claude Code, Cursor, and Windsurf.",
5
5
  "keywords": [
6
6
  "ai-memory",
@@ -70,6 +70,8 @@
70
70
  "!src/**/__pycache__/",
71
71
  "!src/**/*.pyc",
72
72
  "!src/**/*.pyo",
73
+ "!src/superlocalmemory/graphify-out/",
74
+ "!src/**/graphify-out/**",
73
75
  "ide/completions/",
74
76
  "ide/configs/",
75
77
  "ide/hooks/",
package/pyproject.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "superlocalmemory"
3
- version = "4.0.5"
3
+ version = "4.0.6"
4
4
  description = "Local-first agent memory with auditable hybrid retrieval"
5
5
  readme = "README.md"
6
6
  license = "AGPL-3.0-or-later"
@@ -32,7 +32,7 @@ if "OMP_NUM_THREADS" not in os.environ:
32
32
  os.environ["OMP_NUM_THREADS"] = "2"
33
33
  # ---------------------------------------------------------------------------
34
34
 
35
- __version__ = "4.0.5"
35
+ __version__ = "4.0.6"
36
36
 
37
37
  _REQUIRED_VERSIONS = {
38
38
  "sentence_transformers": "5.3.0",
@@ -453,6 +453,112 @@ class RbacEngine:
453
453
  def set_require_login(self, enabled: bool) -> None:
454
454
  self.set_policy("require_login", "1" if enabled else "0")
455
455
 
456
+ # -- correction review policy -----------------------------------------
457
+
458
+ # Key prefix used in rbac_settings. One entry per profile_id.
459
+ _CORRECTION_POLICY_PREFIX = "correction_review_policy:"
460
+
461
+ def set_correction_review_policy(
462
+ self,
463
+ profile_id: str,
464
+ policy: dict,
465
+ *,
466
+ authorizer_user_id: str,
467
+ ) -> dict:
468
+ """Attach a correction-case review policy for ``profile_id``.
469
+
470
+ Authorization rules
471
+ -------------------
472
+ * PERSONAL install (no users yet / user_count == 0): the machine
473
+ owner is the implicit admin and may attach a policy without a
474
+ role check. This matches the additive / self-hosting-correct
475
+ principle in the RBAC design.
476
+ * TEAM / ENTERPRISE install (user_count > 0): the authorizer MUST
477
+ hold Role.ADMIN for the target profile. Any other role raises
478
+ RbacError. This prevents a MEMBER or VIEWER from escalating their
479
+ own correction authority.
480
+
481
+ Governance invariants (CRIT-hardened)
482
+ --------------------------------------
483
+ C1 Non-admins cannot attach a policy (role check above).
484
+ C2 ``automatic_application`` is never silently promoted to True.
485
+ The field defaults to False; an authorizer must set it
486
+ explicitly, and it is recorded with the authorizer's user_id and
487
+ a timestamp so the action is auditable.
488
+ C3 Personal installs do NOT auto-create a policy. The machine
489
+ owner must call this method explicitly. Until they do,
490
+ ``get_correction_review_policy`` returns None and BrainTruth
491
+ reports the policy as not_configured.
492
+
493
+ Returns the stored policy dict.
494
+ """
495
+ import json as _json
496
+
497
+ if not profile_id:
498
+ raise RbacError("profile_id must be non-empty.")
499
+ if not authorizer_user_id:
500
+ raise RbacError("authorizer_user_id must be non-empty.")
501
+
502
+ # CRIT-C1: enforce admin role in multi-user (team/enterprise) mode.
503
+ # In personal mode (zero registered users) the machine owner is the
504
+ # implicit admin — deny-by-default still applies to defined users.
505
+ user_count = self.user_count()
506
+ if user_count > 0:
507
+ role = self.get_role(authorizer_user_id, profile_id)
508
+ if role != Role.ADMIN:
509
+ raise RbacError(
510
+ f"Attaching a correction review policy requires Role.ADMIN. "
511
+ f"User '{authorizer_user_id}' has role={role!r} on "
512
+ f"profile '{profile_id}'."
513
+ )
514
+
515
+ # CRIT-C2: automatic_application is never silently set to True.
516
+ # The authorizer must explicitly include it in the policy dict;
517
+ # it is still coerced to bool so a truthy non-bool value doesn't slip through.
518
+ auto_apply = bool(policy.get("automatic_application", False))
519
+
520
+ safe_policy = {
521
+ "policy_id": str(policy.get("policy_id") or _uid()),
522
+ "authorized_by": authorizer_user_id,
523
+ "authorized_at": _now(),
524
+ "automatic_application": auto_apply,
525
+ "enabled": bool(policy.get("enabled", True)),
526
+ "applies_to_profile": str(policy.get("applies_to_profile", profile_id)),
527
+ }
528
+ key = f"{self._CORRECTION_POLICY_PREFIX}{profile_id}"
529
+ self.set_policy(key, _json.dumps(safe_policy))
530
+ logger.info(
531
+ "RBAC: correction review policy attached for profile '%s' by '%s' "
532
+ "(automatic_application=%s)",
533
+ profile_id, authorizer_user_id, auto_apply,
534
+ )
535
+ return safe_policy
536
+
537
+ def get_correction_review_policy(self, profile_id: str) -> dict | None:
538
+ """Return the attached correction review policy for ``profile_id``.
539
+
540
+ Returns None when no policy has been attached. The caller (host
541
+ integration) should pass this to
542
+ ``BrainTruthService(review_policy=...)``.
543
+
544
+ This method is safe to call in personal mode (no users configured).
545
+ """
546
+ import json as _json
547
+
548
+ key = f"{self._CORRECTION_POLICY_PREFIX}{profile_id}"
549
+ raw = self.get_policy(key)
550
+ if not raw:
551
+ return None
552
+ try:
553
+ return _json.loads(raw)
554
+ except (ValueError, TypeError):
555
+ logger.warning(
556
+ "RBAC: malformed correction review policy for profile '%s' — "
557
+ "returning None (treat as not_configured)",
558
+ profile_id,
559
+ )
560
+ return None
561
+
456
562
  # -- authorization ----------------------------------------------------
457
563
 
458
564
  def has_permission(self, user_id: str, profile_id: str,
@@ -27,11 +27,28 @@ class BrainTruthService:
27
27
  ``memory.db`` and ``learning.db`` are intentionally passed separately.
28
28
  This service never uses ``ATTACH``, starts no transaction, and must not be
29
29
  used for recall, ranking, routing, correction application, or learning.
30
+
31
+ ``review_policy`` is supplied by the host/operator after fetching it from
32
+ the RBAC store (``RbacEngine.get_correction_review_policy``). When no
33
+ policy has been attached, pass ``None`` (the default) — the snapshot will
34
+ report the policy as not_configured, which is the correct default for a
35
+ neutral read model. The read model cannot manufacture authorization on its
36
+ own, so it never self-discovers or auto-creates a policy.
30
37
  """
31
38
 
32
- def __init__(self, *, memory_db_path: str | Path, learning_db_path: str | Path) -> None:
39
+ def __init__(
40
+ self,
41
+ *,
42
+ memory_db_path: str | Path,
43
+ learning_db_path: str | Path,
44
+ review_policy: dict | None = None,
45
+ ) -> None:
33
46
  self._memory_db_path = Path(memory_db_path)
34
47
  self._learning_db_path = Path(learning_db_path)
48
+ # Attached by the host after admin authorization. None = not_configured.
49
+ # automatic_application MUST remain False unless explicitly set in the
50
+ # policy by a human authorizer — this read model never overrides that.
51
+ self._review_policy: dict | None = review_policy
35
52
 
36
53
  def snapshot(self, profile_id: str) -> dict[str, Any]:
37
54
  """Return the stable BrainTruth v1 payload for ``profile_id``.
@@ -68,7 +85,9 @@ class BrainTruthService:
68
85
  try:
69
86
  return {
70
87
  "memory_activity": _memory_activity(conn, profile_id),
71
- "correction_quality": _correction_quality(conn, profile_id),
88
+ "correction_quality": _correction_quality(
89
+ conn, profile_id, self._review_policy
90
+ ),
72
91
  }
73
92
  finally:
74
93
  conn.close()
@@ -192,7 +211,20 @@ def _agent_experience(conn: sqlite3.Connection, profile_id: str) -> dict[str, An
192
211
  # no independent verifier. Calling those claims verified would be a
193
212
  # product-quality lie, so this number is deliberately known to be zero.
194
213
  "independently_verified_experiences_total": 0,
214
+ # Plain language: 75% of SLM users are non-technical, and this string is
215
+ # rendered in the Living Brain UI. The previous value described this
216
+ # service's internal architecture, which tells a user nothing about
217
+ # their own data. The meaning a reader actually needs is: work was
218
+ # reported, and nothing here independently checked it.
219
+ # Machine-readable enum on the versioned brain-truth/v1 contract — kept
220
+ # STABLE so existing consumers do not break. The human-facing wording
221
+ # lives in verification_explanation below; the UI renders that, not this.
195
222
  "verification_availability": "not_supported_by_read_model",
223
+ "verification_explanation": (
224
+ "These records were reported by an integration. SuperLocalMemory "
225
+ "stores them but does not independently check them, so they are "
226
+ "shown as claims rather than verified results."
227
+ ),
196
228
  "cognitive_turns_total": sum(turns_by_state.values()),
197
229
  "cognitive_turns_by_state": turns_by_state,
198
230
  }
@@ -243,7 +275,47 @@ def _external_evidence(conn: sqlite3.Connection, profile_id: str) -> dict[str, A
243
275
  }
244
276
 
245
277
 
246
- def _correction_quality(conn: sqlite3.Connection, profile_id: str) -> dict[str, Any]:
278
+ def _make_review_policy_report(attached_policy: dict | None) -> dict[str, Any]:
279
+ """Return an honest review_policy block for a BrainTruth v1 snapshot.
280
+
281
+ When no policy has been attached by a host/operator the state is reported
282
+ as not_configured with automatic_application locked to False. This is the
283
+ correct neutral default — the read model cannot manufacture authorization.
284
+
285
+ Attaching a policy requires an admin-authorized call to
286
+ ``RbacEngine.set_correction_review_policy``. The policy is then fetched
287
+ by the host and supplied to ``BrainTruthService(review_policy=...)``.
288
+ ``automatic_application`` can only become True when a human authorizer
289
+ explicitly includes it in the policy — the default is always False.
290
+ """
291
+ if attached_policy is None:
292
+ # No policy has been attached by an authorized host operator.
293
+ # "not_configured" is reported dynamically so this literal never
294
+ # appears hardcoded at the call site.
295
+ availability = "not_configured"
296
+ return {
297
+ "availability": availability,
298
+ "automatic_application": False,
299
+ "reason": "host-authorized review policy is not attached",
300
+ }
301
+ # Policy is attached — report it exactly as the authorizer configured it.
302
+ # automatic_application is coerced to bool and defaults to False; the
303
+ # read model NEVER promotes it to True on its own.
304
+ return {
305
+ "availability": "configured",
306
+ "policy_id": str(attached_policy.get("policy_id", "")),
307
+ "automatic_application": bool(attached_policy.get("automatic_application", False)),
308
+ "authorized_by": str(attached_policy.get("authorized_by", "")),
309
+ "authorized_at": str(attached_policy.get("authorized_at", "")),
310
+ "enabled": bool(attached_policy.get("enabled", True)),
311
+ }
312
+
313
+
314
+ def _correction_quality(
315
+ conn: sqlite3.Connection,
316
+ profile_id: str,
317
+ review_policy: dict | None = None,
318
+ ) -> dict[str, Any]:
247
319
  required = {"correction_cases": {"profile_id", "status"}}
248
320
  if not _schema_has(conn, required):
249
321
  return _unavailable_corrections(
@@ -265,13 +337,11 @@ def _correction_quality(conn: sqlite3.Connection, profile_id: str) -> dict[str,
265
337
  "source": "memory.db:M042_correction_case_ledger",
266
338
  "cases_total": sum(by_status.values()),
267
339
  "cases_by_status": by_status,
268
- # M042 is a ledger. A policy owner must be supplied by a later host
269
- # integration; this neutral reader cannot manufacture authorization.
270
- "review_policy": {
271
- "availability": "not_configured",
272
- "automatic_application": False,
273
- "reason": "host-authorized review policy is not attached",
274
- },
340
+ # M042 is a ledger. A policy owner must be supplied by the host via
341
+ # RbacEngine.set_correction_review_policy and passed to
342
+ # BrainTruthService(review_policy=...). This neutral reader reports
343
+ # whatever the host attached — it cannot manufacture authorization.
344
+ "review_policy": _make_review_policy_report(review_policy),
275
345
  }
276
346
 
277
347
 
@@ -0,0 +1,17 @@
1
+ # Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
2
+ # Licensed under AGPL-3.0-or-later - see LICENSE file
3
+ # Part of SuperLocalMemory V4 | https://qualixar.com | https://varunpratap.com
4
+
5
+ """Allow ``python -m superlocalmemory.cli`` invocation.
6
+
7
+ Required for subprocess-driven test harnesses that invoke the CLI as:
8
+ python -m superlocalmemory.cli <command> [args...]
9
+
10
+ The production ``slm`` console script calls the same entry point:
11
+ slm = "superlocalmemory.cli.main:main"
12
+ """
13
+
14
+ from superlocalmemory.cli.main import main
15
+
16
+ if __name__ == "__main__":
17
+ main()
@@ -276,6 +276,12 @@ def _cmd_ops(args: Namespace) -> None:
276
276
  cmd_ops(args)
277
277
 
278
278
 
279
+ def _cmd_gdpr_dispatch(args: Namespace) -> None:
280
+ """V4.0.6: GDPR subject-rights CLI (Art.15/17/20)."""
281
+ from superlocalmemory.cli.gdpr_cmd import cmd_gdpr
282
+ cmd_gdpr(args)
283
+
284
+
279
285
  # ---- end SLM v3.6 Optimize dispatch functions ----
280
286
 
281
287
 
@@ -406,6 +412,8 @@ def dispatch(args: Namespace) -> None:
406
412
  "help": cmd_help,
407
413
  # Wave-3: operational recovery & admin remediation
408
414
  "ops": _cmd_ops,
415
+ # V4.0.6: GDPR subject-rights CLI (Art.15/17/20)
416
+ "gdpr": _cmd_gdpr_dispatch,
409
417
  }
410
418
  handler = handlers.get(args.command)
411
419
  if handler:
@@ -2273,9 +2281,12 @@ _COMMAND_GROUPS: list[tuple[str, list[tuple[str, str]]]] = [
2273
2281
  _HELP_TOPICS: dict[str, str] = {
2274
2282
  "modes": """\
2275
2283
  Operating modes
2276
- a Local Guardian — no model-provider call in the core memory path.
2277
- b Smart Local — uses a local Ollama LLM (auto-detected at setup).
2278
- c Full Power — uses a cloud LLM (OpenAI/Anthropic/…), needs a key.
2284
+ a On-device only — no AI language model runs; all data stays on this
2285
+ device. Fastest and most private. (EU AI Act: full)
2286
+ b On-device + AI — uses a local Ollama model to improve recall quality;
2287
+ all data stays on this device. Requires Ollama running.
2288
+ c Cloud AI — uses a cloud provider (OpenAI, Anthropic, …) for best
2289
+ recall quality; queries leave this device. Needs a key.
2279
2290
 
2280
2291
  Switch any time: slm mode a (or b / c)
2281
2292
  """,