superlocalmemory 3.7.8 → 3.8.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 (260) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/ATTRIBUTION.md +1 -3
  3. package/CHANGELOG.md +69 -0
  4. package/README.md +199 -29
  5. package/package.json +4 -2
  6. package/plugin/.claude-plugin/plugin.json +2 -2
  7. package/plugin/CLAUDE.md +8 -8
  8. package/plugin/agents/slm-governance-advisor.md +80 -0
  9. package/plugin/agents/slm-loop-runner.md +71 -0
  10. package/plugin/agents/slm-memory-advisor.md +10 -5
  11. package/plugin/agents/slm-optimize-advisor.md +9 -3
  12. package/plugin/commands/slm-loop.md +31 -0
  13. package/plugin/hooks/hooks.json +79 -0
  14. package/plugin/requirements.txt +1 -1
  15. package/plugin/scripts/slm-launch +46 -7
  16. package/plugin/settings.json +9 -0
  17. package/plugin/skills/slm-cache/SKILL.md +9 -1
  18. package/plugin/skills/slm-compress/SKILL.md +8 -1
  19. package/plugin/skills/slm-governance/SKILL.md +248 -0
  20. package/plugin/skills/slm-graph/SKILL.md +17 -3
  21. package/plugin/skills/slm-loop/SKILL.md +99 -0
  22. package/plugin/skills/slm-mesh/SKILL.md +282 -0
  23. package/plugin/skills/slm-profile/SKILL.md +148 -0
  24. package/plugin/skills/slm-recall/SKILL.md +46 -10
  25. package/plugin/skills/slm-remember/SKILL.md +48 -1
  26. package/plugin/skills/slm-scope/SKILL.md +176 -0
  27. package/plugin/skills/slm-session/SKILL.md +24 -1
  28. package/plugin/skills/slm-status/SKILL.md +18 -1
  29. package/plugin-src/agents/slm-governance-advisor.md +80 -0
  30. package/plugin-src/agents/slm-loop-runner.md +71 -0
  31. package/plugin-src/agents/slm-memory-advisor.md +10 -5
  32. package/plugin-src/agents/slm-optimize-advisor.md +9 -3
  33. package/plugin-src/commands/slm-loop.md +31 -0
  34. package/plugin-src/hooks/hooks.json +79 -0
  35. package/plugin-src/manifest.json +7 -2
  36. package/plugin-src/requirements.txt +1 -1
  37. package/plugin-src/rules/AGENTS.md +57 -18
  38. package/plugin-src/rules/CLAUDE.md.fragment +8 -8
  39. package/plugin-src/scripts/slm-launch +46 -7
  40. package/plugin-src/settings.json +9 -0
  41. package/plugin-src/skills/slm-cache/SKILL.md +9 -1
  42. package/plugin-src/skills/slm-compress/SKILL.md +8 -1
  43. package/plugin-src/skills/slm-governance/SKILL.md +248 -0
  44. package/plugin-src/skills/slm-graph/SKILL.md +17 -3
  45. package/plugin-src/skills/slm-loop/SKILL.md +99 -0
  46. package/plugin-src/skills/slm-mesh/SKILL.md +282 -0
  47. package/plugin-src/skills/slm-profile/SKILL.md +148 -0
  48. package/plugin-src/skills/slm-recall/SKILL.md +46 -10
  49. package/plugin-src/skills/slm-remember/SKILL.md +48 -1
  50. package/plugin-src/skills/slm-scope/SKILL.md +176 -0
  51. package/plugin-src/skills/slm-session/SKILL.md +24 -1
  52. package/plugin-src/skills/slm-status/SKILL.md +18 -1
  53. package/pyproject.toml +1 -1
  54. package/scripts/postinstall/validation.js +2 -0
  55. package/scripts/postinstall-interactive.js +74 -2
  56. package/src/superlocalmemory/__init__.py +1 -1
  57. package/src/superlocalmemory/access/__init__.py +3 -0
  58. package/src/superlocalmemory/access/rbac.py +477 -0
  59. package/src/superlocalmemory/cli/commands.py +94 -10
  60. package/src/superlocalmemory/cli/compress_cmd.py +17 -7
  61. package/src/superlocalmemory/cli/loop_cmd.py +192 -0
  62. package/src/superlocalmemory/cli/main.py +39 -4
  63. package/src/superlocalmemory/cli/mesh_cmd.py +38 -0
  64. package/src/superlocalmemory/cli/optimize_cmd.py +3 -0
  65. package/src/superlocalmemory/cli/pending_store.py +49 -13
  66. package/src/superlocalmemory/cli/proxy_cmd.py +4 -0
  67. package/src/superlocalmemory/cli/scale_engine_cmd.py +6 -0
  68. package/src/superlocalmemory/cli/setup_wizard.py +22 -13
  69. package/src/superlocalmemory/compliance/audit.py +6 -0
  70. package/src/superlocalmemory/compliance/gdpr.py +128 -138
  71. package/src/superlocalmemory/compliance/retention.py +176 -45
  72. package/src/superlocalmemory/core/backend_orchestrator.py +5 -43
  73. package/src/superlocalmemory/core/community_summary.py +267 -0
  74. package/src/superlocalmemory/core/config.py +216 -3
  75. package/src/superlocalmemory/core/consolidation_engine.py +95 -22
  76. package/src/superlocalmemory/core/context_cache.py +61 -18
  77. package/src/superlocalmemory/core/embedding_worker.py +17 -2
  78. package/src/superlocalmemory/core/embeddings.py +12 -1
  79. package/src/superlocalmemory/core/engine.py +17 -1
  80. package/src/superlocalmemory/core/engine_ingestion.py +29 -0
  81. package/src/superlocalmemory/core/engine_wiring.py +13 -0
  82. package/src/superlocalmemory/core/entity_community.py +178 -0
  83. package/src/superlocalmemory/core/graph_analyzer.py +39 -2
  84. package/src/superlocalmemory/core/graph_pruner.py +13 -8
  85. package/src/superlocalmemory/core/key_expander.py +138 -0
  86. package/src/superlocalmemory/core/maintenance.py +23 -0
  87. package/src/superlocalmemory/core/modes.py +1 -1
  88. package/src/superlocalmemory/core/mutations.py +2 -2
  89. package/src/superlocalmemory/core/pii.py +105 -0
  90. package/src/superlocalmemory/core/progressive_abstraction.py +208 -0
  91. package/src/superlocalmemory/core/recall_pipeline.py +2 -0
  92. package/src/superlocalmemory/core/recall_worker.py +20 -6
  93. package/src/superlocalmemory/core/scale_engine.py +60 -1
  94. package/src/superlocalmemory/core/security_primitives.py +40 -2
  95. package/src/superlocalmemory/core/store_pipeline.py +35 -11
  96. package/src/superlocalmemory/core/worker_pool.py +21 -6
  97. package/src/superlocalmemory/encoding/entity_reflexion.py +200 -0
  98. package/src/superlocalmemory/encoding/entity_resolver.py +34 -24
  99. package/src/superlocalmemory/encoding/fact_extractor.py +26 -1
  100. package/src/superlocalmemory/encoding/temporal_validator.py +64 -1
  101. package/src/superlocalmemory/evolution/evolution_store.py +122 -45
  102. package/src/superlocalmemory/evolution/llm_dispatch.py +12 -1
  103. package/src/superlocalmemory/evolution/model_selection.py +160 -0
  104. package/src/superlocalmemory/evolution/mutation_generator.py +16 -0
  105. package/src/superlocalmemory/evolution/skill_evolver.py +127 -42
  106. package/src/superlocalmemory/evolution/triggers.py +22 -13
  107. package/src/superlocalmemory/graph/cozo_backend.py +43 -20
  108. package/src/superlocalmemory/hooks/adapter_base.py +5 -1
  109. package/src/superlocalmemory/hooks/auto_recall.py +13 -1
  110. package/src/superlocalmemory/hooks/claude_code_hooks.py +11 -0
  111. package/src/superlocalmemory/hooks/codex_assets.py +64 -5
  112. package/src/superlocalmemory/hooks/hook_daemon.py +20 -3
  113. package/src/superlocalmemory/hooks/memory_protocol.py +54 -0
  114. package/src/superlocalmemory/hooks/portable_kit.py +114 -1
  115. package/src/superlocalmemory/infra/backup.py +12 -1
  116. package/src/superlocalmemory/infra/daemon_identity.py +40 -4
  117. package/src/superlocalmemory/infra/data_root.py +43 -4
  118. package/src/superlocalmemory/infra/event_bus.py +107 -24
  119. package/src/superlocalmemory/infra/rate_limiter.py +93 -0
  120. package/src/superlocalmemory/ingestion/adapter_manager.py +4 -1
  121. package/src/superlocalmemory/ingestion/credentials.py +1 -1
  122. package/src/superlocalmemory/learning/cross_project.py +28 -19
  123. package/src/superlocalmemory/learning/reward_proxy.py +42 -9
  124. package/src/superlocalmemory/loops/__init__.py +56 -0
  125. package/src/superlocalmemory/loops/budget.py +58 -0
  126. package/src/superlocalmemory/loops/engine.py +164 -0
  127. package/src/superlocalmemory/loops/ledger.py +243 -0
  128. package/src/superlocalmemory/loops/models.py +152 -0
  129. package/src/superlocalmemory/loops/rules.py +52 -0
  130. package/src/superlocalmemory/mcp/_daemon_proxy.py +3 -0
  131. package/src/superlocalmemory/mcp/_pool_adapter.py +2 -0
  132. package/src/superlocalmemory/mcp/profiles.py +103 -0
  133. package/src/superlocalmemory/mcp/server.py +21 -49
  134. package/src/superlocalmemory/mcp/tools_active.py +4 -7
  135. package/src/superlocalmemory/mcp/tools_code_graph.py +51 -5
  136. package/src/superlocalmemory/mcp/tools_core.py +8 -1
  137. package/src/superlocalmemory/mcp/tools_evolution.py +6 -3
  138. package/src/superlocalmemory/mcp/tools_loops.py +300 -0
  139. package/src/superlocalmemory/mcp/tools_mesh.py +140 -4
  140. package/src/superlocalmemory/mcp/tools_optimize.py +15 -8
  141. package/src/superlocalmemory/mesh/broker.py +237 -129
  142. package/src/superlocalmemory/mesh/remote_sync.py +50 -8
  143. package/src/superlocalmemory/optimize/NOTICE +1 -6
  144. package/src/superlocalmemory/optimize/adapters/anthropic_adapter.py +1 -4
  145. package/src/superlocalmemory/optimize/adapters/openai_adapter.py +1 -4
  146. package/src/superlocalmemory/optimize/cache/semantic.py +27 -19
  147. package/src/superlocalmemory/optimize/compress/align.py +32 -26
  148. package/src/superlocalmemory/optimize/compress/ccr.py +14 -71
  149. package/src/superlocalmemory/optimize/compress/router.py +105 -22
  150. package/src/superlocalmemory/optimize/config/defaults.py +1 -1
  151. package/src/superlocalmemory/optimize/config/schema.py +87 -4
  152. package/src/superlocalmemory/optimize/metrics/counters.py +13 -4
  153. package/src/superlocalmemory/optimize/metrics/estimator.py +0 -3
  154. package/src/superlocalmemory/optimize/proxy/_helpers.py +31 -4
  155. package/src/superlocalmemory/optimize/storage/db.py +38 -9
  156. package/src/superlocalmemory/optimize/storage/schema.py +10 -0
  157. package/src/superlocalmemory/parameterization/pattern_extractor.py +6 -3
  158. package/src/superlocalmemory/retrieval/agentic.py +1 -1
  159. package/src/superlocalmemory/retrieval/bm25_channel.py +68 -10
  160. package/src/superlocalmemory/retrieval/engine.py +168 -26
  161. package/src/superlocalmemory/retrieval/entity_channel.py +7 -5
  162. package/src/superlocalmemory/retrieval/hopfield_channel.py +9 -2
  163. package/src/superlocalmemory/retrieval/semantic_channel.py +114 -21
  164. package/src/superlocalmemory/retrieval/spreading_activation.py +11 -2
  165. package/src/superlocalmemory/retrieval/temporal_channel.py +48 -9
  166. package/src/superlocalmemory/retrieval/temporal_frame.py +102 -0
  167. package/src/superlocalmemory/retrieval/temporal_validity_filter.py +135 -0
  168. package/src/superlocalmemory/retrieval/time_window.py +181 -0
  169. package/src/superlocalmemory/server/api.py +4 -4
  170. package/src/superlocalmemory/server/profile_runtime.py +125 -8
  171. package/src/superlocalmemory/server/rbac_enforce.py +142 -0
  172. package/src/superlocalmemory/server/recall_health.py +24 -3
  173. package/src/superlocalmemory/server/recall_serializer.py +19 -1
  174. package/src/superlocalmemory/server/routes/abstraction.py +115 -0
  175. package/src/superlocalmemory/server/routes/agents.py +128 -38
  176. package/src/superlocalmemory/server/routes/backup.py +34 -10
  177. package/src/superlocalmemory/server/routes/behavioral.py +13 -12
  178. package/src/superlocalmemory/server/routes/brain.py +21 -5
  179. package/src/superlocalmemory/server/routes/chat.py +10 -5
  180. package/src/superlocalmemory/server/routes/compliance.py +171 -21
  181. package/src/superlocalmemory/server/routes/config_api.py +436 -0
  182. package/src/superlocalmemory/server/routes/data_io.py +30 -8
  183. package/src/superlocalmemory/server/routes/entity.py +9 -4
  184. package/src/superlocalmemory/server/routes/events.py +24 -8
  185. package/src/superlocalmemory/server/routes/evolution.py +135 -17
  186. package/src/superlocalmemory/server/routes/helpers.py +16 -1
  187. package/src/superlocalmemory/server/routes/ingest.py +7 -4
  188. package/src/superlocalmemory/server/routes/insights.py +3 -3
  189. package/src/superlocalmemory/server/routes/learning.py +14 -14
  190. package/src/superlocalmemory/server/routes/lifecycle.py +59 -8
  191. package/src/superlocalmemory/server/routes/memories.py +182 -57
  192. package/src/superlocalmemory/server/routes/mesh.py +95 -15
  193. package/src/superlocalmemory/server/routes/optimize.py +33 -1
  194. package/src/superlocalmemory/server/routes/prewarm.py +2 -0
  195. package/src/superlocalmemory/server/routes/profiles.py +63 -17
  196. package/src/superlocalmemory/server/routes/ratelimit.py +124 -0
  197. package/src/superlocalmemory/server/routes/rbac.py +367 -0
  198. package/src/superlocalmemory/server/routes/stats.py +13 -6
  199. package/src/superlocalmemory/server/routes/tiers.py +11 -9
  200. package/src/superlocalmemory/server/routes/v3_api.py +183 -69
  201. package/src/superlocalmemory/server/routes/ws.py +5 -2
  202. package/src/superlocalmemory/server/security_middleware.py +12 -5
  203. package/src/superlocalmemory/server/ui.py +20 -5
  204. package/src/superlocalmemory/server/unified_daemon.py +384 -56
  205. package/src/superlocalmemory/server/write_identity.py +38 -8
  206. package/src/superlocalmemory/storage/database.py +265 -53
  207. package/src/superlocalmemory/storage/migration_runner.py +53 -0
  208. package/src/superlocalmemory/storage/migrations/M021_ingestion_log_profile.py +108 -0
  209. package/src/superlocalmemory/storage/migrations/M022_entity_aliases_profile.py +86 -0
  210. package/src/superlocalmemory/storage/migrations/M023_mesh_profile_isolation.py +194 -0
  211. package/src/superlocalmemory/storage/migrations/M024_rbac_users_roles.py +87 -0
  212. package/src/superlocalmemory/storage/migrations/M025_perf_indexes.py +90 -0
  213. package/src/superlocalmemory/storage/migrations/M026_rbac_memberships_fk.py +136 -0
  214. package/src/superlocalmemory/storage/migrations/M027_transferable_patterns_profile.py +163 -0
  215. package/src/superlocalmemory/storage/models.py +4 -0
  216. package/src/superlocalmemory/storage/schema.py +87 -0
  217. package/src/superlocalmemory/storage/schema_v343.py +24 -12
  218. package/src/superlocalmemory/trust/gate.py +49 -8
  219. package/src/superlocalmemory/ui/assets/slm-icon-white.svg +64 -0
  220. package/src/superlocalmemory/ui/assets/slm-icon.svg +36 -0
  221. package/src/superlocalmemory/ui/css/design-system.css +621 -0
  222. package/src/superlocalmemory/ui/css/neural-glass.css +6 -0
  223. package/src/superlocalmemory/ui/css/od-bridge.css +158 -0
  224. package/src/superlocalmemory/ui/favicon.svg +35 -4
  225. package/src/superlocalmemory/ui/index.html +306 -173
  226. package/src/superlocalmemory/ui/js/brain.js +5 -20
  227. package/src/superlocalmemory/ui/js/core.js +47 -31
  228. package/src/superlocalmemory/ui/js/dashboard.js +314 -63
  229. package/src/superlocalmemory/ui/js/event-delegation.js +102 -0
  230. package/src/superlocalmemory/ui/js/knowledge-graph.js +11 -11
  231. package/src/superlocalmemory/ui/js/math-health.js +1 -1
  232. package/src/superlocalmemory/ui/js/memories.js +15 -4
  233. package/src/superlocalmemory/ui/js/memory-chat.js +7 -7
  234. package/src/superlocalmemory/ui/js/ng-entities.js +6 -8
  235. package/src/superlocalmemory/ui/js/ng-ingestion.js +4 -4
  236. package/src/superlocalmemory/ui/js/ng-mesh.js +4 -9
  237. package/src/superlocalmemory/ui/js/ng-shell.js +8 -8
  238. package/src/superlocalmemory/ui/js/ng-skills.js +54 -2
  239. package/src/superlocalmemory/ui/js/od-agents.js +544 -0
  240. package/src/superlocalmemory/ui/js/od-auth-gate.js +257 -0
  241. package/src/superlocalmemory/ui/js/od-backup.js +780 -0
  242. package/src/superlocalmemory/ui/js/od-brain.js +779 -0
  243. package/src/superlocalmemory/ui/js/od-entities.js +579 -0
  244. package/src/superlocalmemory/ui/js/od-graph.js +593 -0
  245. package/src/superlocalmemory/ui/js/od-health.js +539 -0
  246. package/src/superlocalmemory/ui/js/od-mcp.js +508 -0
  247. package/src/superlocalmemory/ui/js/od-memories.js +887 -0
  248. package/src/superlocalmemory/ui/js/od-mesh.js +539 -0
  249. package/src/superlocalmemory/ui/js/od-operations.js +1250 -0
  250. package/src/superlocalmemory/ui/js/od-optimize.js +787 -0
  251. package/src/superlocalmemory/ui/js/od-settings.js +1053 -0
  252. package/src/superlocalmemory/ui/js/od-shell.js +593 -0
  253. package/src/superlocalmemory/ui/js/od-skills.js +573 -0
  254. package/src/superlocalmemory/ui/js/od-team.js +258 -0
  255. package/src/superlocalmemory/ui/js/profiles.js +159 -46
  256. package/src/superlocalmemory/ui/js/settings.js +2 -2
  257. package/src/superlocalmemory/ui/js/timeline.js +34 -5
  258. package/src/superlocalmemory/ui/js/trust-dashboard.js +2 -2
  259. package/src/superlocalmemory/vector/lancedb_backend.py +8 -6
  260. package/src/superlocalmemory/learning/behavioral_listener.py +0 -94
@@ -13,14 +13,20 @@ import logging
13
13
  import sqlite3
14
14
  from typing import Optional
15
15
 
16
- from fastapi import APIRouter, Query
16
+ from fastapi import APIRouter, Query, Request
17
+ from fastapi.responses import JSONResponse
17
18
 
18
- from .helpers import get_active_profile, MEMORY_DIR, DB_PATH
19
+ from .helpers import get_active_profile, get_engine_lazy, MEMORY_DIR, DB_PATH
20
+ from superlocalmemory.server.route_mutations import authorize_route_mutation
19
21
 
20
22
  logger = logging.getLogger("superlocalmemory.routes.compliance")
21
23
  router = APIRouter()
22
24
 
23
- AUDIT_DB = MEMORY_DIR / "audit.db"
25
+ # The engine's audit post-hooks write to <data_root>/audit_chain.db
26
+ # (core/engine_wiring.py). The route MUST read that same file — an earlier
27
+ # "audit.db" path pointed at a file nothing ever wrote, so the Compliance tab
28
+ # always showed zero events.
29
+ AUDIT_DB = MEMORY_DIR / "audit_chain.db"
24
30
 
25
31
  # Feature detection
26
32
  COMPLIANCE_AVAILABLE = False
@@ -28,6 +34,7 @@ try:
28
34
  from superlocalmemory.compliance.audit import AuditChain
29
35
  from superlocalmemory.compliance.retention import RetentionEngine
30
36
  from superlocalmemory.compliance.abac import ABACEngine
37
+ from superlocalmemory.compliance.gdpr import GDPRCompliance
31
38
  COMPLIANCE_AVAILABLE = True
32
39
  except ImportError:
33
40
  logger.info("V3 compliance engine not available")
@@ -42,22 +49,24 @@ async def compliance_status():
42
49
  try:
43
50
  profile = get_active_profile()
44
51
 
45
- # Audit events
52
+ # Audit events (hash-chained, from the engine's live audit_chain.db).
53
+ # Scope the count + recent list to the active profile.
46
54
  audit_events_count = 0
47
55
  recent_audit_events = []
48
56
  try:
49
57
  audit = AuditChain(str(AUDIT_DB))
50
- audit_events_count = audit.count_events()
51
- recent_audit_events = audit.get_recent_events(limit=30)
58
+ recent_audit_events = audit.query(profile_id=profile, limit=30)
59
+ # get_stats() is global; count this profile's rows for the header.
60
+ audit_events_count = len(audit.query(profile_id=profile, limit=100000))
52
61
  except Exception as exc:
53
62
  logger.debug("audit chain: %s", exc)
54
63
 
55
- # Retention policies
64
+ # Retention policies (scoped to the active profile)
56
65
  retention_policies = []
57
66
  try:
58
67
  conn = sqlite3.connect(str(DB_PATH))
59
68
  engine = RetentionEngine(conn)
60
- retention_policies = engine.list_rules()
69
+ retention_policies = engine.list_rules(profile)
61
70
  conn.close()
62
71
  except Exception as exc:
63
72
  logger.debug("retention engine: %s", exc)
@@ -78,13 +87,14 @@ async def compliance_status():
78
87
  "retention_policies": retention_policies,
79
88
  "abac_policies_count": abac_policies_count,
80
89
  }
81
- except Exception as e:
82
- logger.error("compliance_status error: %s", e)
83
- return {"available": False, "error": str(e)}
90
+ except Exception:
91
+ logger.exception("compliance_status error")
92
+ return {"available": False, "error": "Internal server error"}
84
93
 
85
94
 
86
95
  @router.get("/api/compliance/audit")
87
96
  async def query_audit_trail(
97
+ request: Request,
88
98
  limit: int = Query(default=50, ge=1, le=500),
89
99
  event_type: Optional[str] = Query(default=None),
90
100
  since: Optional[str] = Query(default=None),
@@ -92,20 +102,33 @@ async def query_audit_trail(
92
102
  """Query audit trail events with optional filters."""
93
103
  if not COMPLIANCE_AVAILABLE:
94
104
  return {"available": False, "error": "Compliance engine not available"}
95
-
105
+ # The audit trail reveals operations, actors, and fact_ids. This GET is not
106
+ # covered by the mutation middleware; the same-origin dashboard reads it via
107
+ # a plain GET (no token header), so gate on the loopback-trusted mutation
108
+ # boundary — local owner allowed, remote uncredentialed caller fails closed.
109
+ from superlocalmemory.server.write_identity import require_http_mutation_actor
110
+ require_http_mutation_actor(request, getattr(request.app.state, "daemon_descriptor", None),
111
+ actor_kind="audit-read")
96
112
  try:
113
+ profile = get_active_profile()
97
114
  audit = AuditChain(str(AUDIT_DB))
98
- events = audit.get_recent_events(
99
- limit=limit, event_type=event_type, since=since,
115
+ events = audit.query(
116
+ profile_id=profile, operation=event_type, start_date=since,
117
+ limit=limit,
100
118
  )
101
-
119
+ chain_ok = True
120
+ try:
121
+ chain_ok = audit.verify_integrity()
122
+ except Exception:
123
+ pass
102
124
  return {
103
125
  "available": True, "events": events, "total": len(events),
126
+ "chain_verified": chain_ok, "active_profile": profile,
104
127
  "filters": {"event_type": event_type, "since": since, "limit": limit},
105
128
  }
106
- except Exception as e:
107
- logger.error("query_audit_trail error: %s", e)
108
- return {"available": False, "error": str(e)}
129
+ except Exception:
130
+ logger.exception("query_audit_trail error")
131
+ return {"available": False, "error": "Internal server error"}
109
132
 
110
133
 
111
134
  @router.post("/api/compliance/retention-policy")
@@ -155,6 +178,133 @@ async def create_retention_policy(data: dict):
155
178
  "active_profile": profile,
156
179
  "message": f"Retention policy '{name}' created ({retention_days}d, {action})",
157
180
  }
158
- except Exception as e:
159
- logger.error("create_retention_policy error: %s", e)
160
- return {"success": False, "error": str(e)}
181
+ except Exception:
182
+ logger.exception("create_retention_policy error")
183
+ return {"success": False, "error": "Internal server error"}
184
+
185
+
186
+ @router.delete("/api/compliance/retention-policy")
187
+ async def delete_retention_policy(name: str = Query(...)):
188
+ """Delete a retention policy by name for the active profile."""
189
+ if not COMPLIANCE_AVAILABLE:
190
+ return {"success": False, "error": "Compliance engine not available"}
191
+ try:
192
+ profile = get_active_profile()
193
+ conn = sqlite3.connect(str(DB_PATH))
194
+ engine = RetentionEngine(conn)
195
+ removed = engine.delete_rule(profile, name)
196
+ conn.close()
197
+ if not removed:
198
+ return {"success": False, "error": f"Policy '{name}' not found"}
199
+ return {"success": True, "active_profile": profile,
200
+ "message": f"Retention policy '{name}' deleted"}
201
+ except Exception:
202
+ logger.exception("delete_retention_policy error")
203
+ return {"success": False, "error": "Internal server error"}
204
+
205
+
206
+ @router.post("/api/compliance/retention/enforce")
207
+ async def enforce_retention():
208
+ """Run all retention policies for the active profile now.
209
+
210
+ Moves expired facts to their rule's terminal lifecycle zone (archive/
211
+ tombstone) or counts them (notify). Soft-state only — never a raw delete.
212
+ """
213
+ if not COMPLIANCE_AVAILABLE:
214
+ return {"success": False, "error": "Compliance engine not available"}
215
+ try:
216
+ profile = get_active_profile()
217
+ conn = sqlite3.connect(str(DB_PATH))
218
+ engine = RetentionEngine(conn)
219
+ result = engine.enforce(profile)
220
+ conn.close()
221
+ return {"success": True, **result}
222
+ except Exception:
223
+ logger.exception("enforce_retention error")
224
+ return {"success": False, "error": "Internal server error"}
225
+
226
+
227
+ # ── GDPR Art. 15/20 — Right to Access / Portability ─────────────────────────
228
+
229
+ @router.get("/api/compliance/gdpr/export")
230
+ async def gdpr_export(request: Request):
231
+ """Export ALL data for the active profile (GDPR Art. 20 portability).
232
+
233
+ Comprehensive 14-table export (memories, facts, entities, edges, trust,
234
+ feedback, behavioral patterns, provenance, audit, …) as a downloadable
235
+ JSON attachment. Read-only.
236
+ """
237
+ if not COMPLIANCE_AVAILABLE:
238
+ return {"available": False, "error": "Compliance engine not available"}
239
+ # A full-workspace data dump is an administrative action. Triggered by a
240
+ # top-level navigation (a.href) that cannot carry a credential header, so
241
+ # gate on the loopback-trusted mutation boundary (local owner allowed,
242
+ # remote uncredentialed fails closed) AND require MANAGE — in company mode
243
+ # a session cookie flows on navigation, so a non-admin user is still denied;
244
+ # the machine owner keeps MANAGE.
245
+ from superlocalmemory.server.write_identity import require_http_mutation_actor
246
+ from superlocalmemory.server.rbac_enforce import require_manage
247
+ require_http_mutation_actor(request, getattr(request.app.state, "daemon_descriptor", None),
248
+ actor_kind="gdpr-export")
249
+ require_manage(request)
250
+ try:
251
+ engine = get_engine_lazy(request.app.state)
252
+ if engine is None:
253
+ return {"available": False, "error": "Engine not initialized"}
254
+ profile = get_active_profile()
255
+ data = GDPRCompliance(engine._db).export_profile_data(profile)
256
+ filename = f"slm-export-{profile}.json"
257
+ return JSONResponse(
258
+ content=data,
259
+ headers={"Content-Disposition": f'attachment; filename="{filename}"'},
260
+ )
261
+ except Exception:
262
+ logger.exception("gdpr_export error")
263
+ return {"available": False, "error": "Internal server error"}
264
+
265
+
266
+ # ── GDPR Art. 17 — Right to Erasure ─────────────────────────────────────────
267
+
268
+ @router.post("/api/compliance/gdpr/erase")
269
+ async def gdpr_erase(request: Request, data: dict = {}):
270
+ """Permanently erase ALL data for the active profile (GDPR Art. 17).
271
+
272
+ IRREVERSIBLE. Requires an explicit ``confirm`` field in the body that
273
+ exactly matches the active profile name — this is the guard against an
274
+ accidental one-click wipe. The 'default' profile can never be erased
275
+ (enforced in GDPRCompliance.forget_profile). Mutation-authorized.
276
+ """
277
+ if not COMPLIANCE_AVAILABLE:
278
+ return {"success": False, "error": "Compliance engine not available"}
279
+ try:
280
+ engine = get_engine_lazy(request.app.state)
281
+ if engine is None:
282
+ return {"success": False, "error": "Engine not initialized"}
283
+ profile = get_active_profile()
284
+ confirm = (data or {}).get("confirm", "")
285
+ if confirm != profile:
286
+ return {
287
+ "success": False,
288
+ "error": (
289
+ "Confirmation required: send {\"confirm\": \"" + profile +
290
+ "\"} to erase this profile. This is irreversible."
291
+ ),
292
+ }
293
+ if profile == "default":
294
+ return {"success": False,
295
+ "error": "The 'default' profile cannot be erased."}
296
+ # Irreversible erasure is admin-only (beyond mutation auth).
297
+ from superlocalmemory.server.rbac_enforce import require_manage
298
+ require_manage(request, profile=profile)
299
+ authorization = authorize_route_mutation(
300
+ request,
301
+ operation="delete",
302
+ source_agent_id="http-gdpr-erase",
303
+ profile_id=profile,
304
+ )
305
+ result = GDPRCompliance(engine._db).forget_profile(profile)
306
+ authorization.complete()
307
+ return {"success": True, "active_profile": profile, **(result or {})}
308
+ except Exception:
309
+ logger.exception("gdpr_erase error")
310
+ return {"success": False, "error": "Internal server error"}