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.
- package/.claude-plugin/marketplace.json +1 -1
- package/ATTRIBUTION.md +1 -3
- package/CHANGELOG.md +69 -0
- package/README.md +199 -29
- package/package.json +4 -2
- package/plugin/.claude-plugin/plugin.json +2 -2
- package/plugin/CLAUDE.md +8 -8
- package/plugin/agents/slm-governance-advisor.md +80 -0
- package/plugin/agents/slm-loop-runner.md +71 -0
- package/plugin/agents/slm-memory-advisor.md +10 -5
- package/plugin/agents/slm-optimize-advisor.md +9 -3
- package/plugin/commands/slm-loop.md +31 -0
- package/plugin/hooks/hooks.json +79 -0
- package/plugin/requirements.txt +1 -1
- package/plugin/scripts/slm-launch +46 -7
- package/plugin/settings.json +9 -0
- package/plugin/skills/slm-cache/SKILL.md +9 -1
- package/plugin/skills/slm-compress/SKILL.md +8 -1
- package/plugin/skills/slm-governance/SKILL.md +248 -0
- package/plugin/skills/slm-graph/SKILL.md +17 -3
- package/plugin/skills/slm-loop/SKILL.md +99 -0
- package/plugin/skills/slm-mesh/SKILL.md +282 -0
- package/plugin/skills/slm-profile/SKILL.md +148 -0
- package/plugin/skills/slm-recall/SKILL.md +46 -10
- package/plugin/skills/slm-remember/SKILL.md +48 -1
- package/plugin/skills/slm-scope/SKILL.md +176 -0
- package/plugin/skills/slm-session/SKILL.md +24 -1
- package/plugin/skills/slm-status/SKILL.md +18 -1
- package/plugin-src/agents/slm-governance-advisor.md +80 -0
- package/plugin-src/agents/slm-loop-runner.md +71 -0
- package/plugin-src/agents/slm-memory-advisor.md +10 -5
- package/plugin-src/agents/slm-optimize-advisor.md +9 -3
- package/plugin-src/commands/slm-loop.md +31 -0
- package/plugin-src/hooks/hooks.json +79 -0
- package/plugin-src/manifest.json +7 -2
- package/plugin-src/requirements.txt +1 -1
- package/plugin-src/rules/AGENTS.md +57 -18
- package/plugin-src/rules/CLAUDE.md.fragment +8 -8
- package/plugin-src/scripts/slm-launch +46 -7
- package/plugin-src/settings.json +9 -0
- package/plugin-src/skills/slm-cache/SKILL.md +9 -1
- package/plugin-src/skills/slm-compress/SKILL.md +8 -1
- package/plugin-src/skills/slm-governance/SKILL.md +248 -0
- package/plugin-src/skills/slm-graph/SKILL.md +17 -3
- package/plugin-src/skills/slm-loop/SKILL.md +99 -0
- package/plugin-src/skills/slm-mesh/SKILL.md +282 -0
- package/plugin-src/skills/slm-profile/SKILL.md +148 -0
- package/plugin-src/skills/slm-recall/SKILL.md +46 -10
- package/plugin-src/skills/slm-remember/SKILL.md +48 -1
- package/plugin-src/skills/slm-scope/SKILL.md +176 -0
- package/plugin-src/skills/slm-session/SKILL.md +24 -1
- package/plugin-src/skills/slm-status/SKILL.md +18 -1
- package/pyproject.toml +1 -1
- package/scripts/postinstall/validation.js +2 -0
- package/scripts/postinstall-interactive.js +74 -2
- package/src/superlocalmemory/__init__.py +1 -1
- package/src/superlocalmemory/access/__init__.py +3 -0
- package/src/superlocalmemory/access/rbac.py +477 -0
- package/src/superlocalmemory/cli/commands.py +94 -10
- package/src/superlocalmemory/cli/compress_cmd.py +17 -7
- package/src/superlocalmemory/cli/loop_cmd.py +192 -0
- package/src/superlocalmemory/cli/main.py +39 -4
- package/src/superlocalmemory/cli/mesh_cmd.py +38 -0
- package/src/superlocalmemory/cli/optimize_cmd.py +3 -0
- package/src/superlocalmemory/cli/pending_store.py +49 -13
- package/src/superlocalmemory/cli/proxy_cmd.py +4 -0
- package/src/superlocalmemory/cli/scale_engine_cmd.py +6 -0
- package/src/superlocalmemory/cli/setup_wizard.py +22 -13
- package/src/superlocalmemory/compliance/audit.py +6 -0
- package/src/superlocalmemory/compliance/gdpr.py +128 -138
- package/src/superlocalmemory/compliance/retention.py +176 -45
- package/src/superlocalmemory/core/backend_orchestrator.py +5 -43
- package/src/superlocalmemory/core/community_summary.py +267 -0
- package/src/superlocalmemory/core/config.py +216 -3
- package/src/superlocalmemory/core/consolidation_engine.py +95 -22
- package/src/superlocalmemory/core/context_cache.py +61 -18
- package/src/superlocalmemory/core/embedding_worker.py +17 -2
- package/src/superlocalmemory/core/embeddings.py +12 -1
- package/src/superlocalmemory/core/engine.py +17 -1
- package/src/superlocalmemory/core/engine_ingestion.py +29 -0
- package/src/superlocalmemory/core/engine_wiring.py +13 -0
- package/src/superlocalmemory/core/entity_community.py +178 -0
- package/src/superlocalmemory/core/graph_analyzer.py +39 -2
- package/src/superlocalmemory/core/graph_pruner.py +13 -8
- package/src/superlocalmemory/core/key_expander.py +138 -0
- package/src/superlocalmemory/core/maintenance.py +23 -0
- package/src/superlocalmemory/core/modes.py +1 -1
- package/src/superlocalmemory/core/mutations.py +2 -2
- package/src/superlocalmemory/core/pii.py +105 -0
- package/src/superlocalmemory/core/progressive_abstraction.py +208 -0
- package/src/superlocalmemory/core/recall_pipeline.py +2 -0
- package/src/superlocalmemory/core/recall_worker.py +20 -6
- package/src/superlocalmemory/core/scale_engine.py +60 -1
- package/src/superlocalmemory/core/security_primitives.py +40 -2
- package/src/superlocalmemory/core/store_pipeline.py +35 -11
- package/src/superlocalmemory/core/worker_pool.py +21 -6
- package/src/superlocalmemory/encoding/entity_reflexion.py +200 -0
- package/src/superlocalmemory/encoding/entity_resolver.py +34 -24
- package/src/superlocalmemory/encoding/fact_extractor.py +26 -1
- package/src/superlocalmemory/encoding/temporal_validator.py +64 -1
- package/src/superlocalmemory/evolution/evolution_store.py +122 -45
- package/src/superlocalmemory/evolution/llm_dispatch.py +12 -1
- package/src/superlocalmemory/evolution/model_selection.py +160 -0
- package/src/superlocalmemory/evolution/mutation_generator.py +16 -0
- package/src/superlocalmemory/evolution/skill_evolver.py +127 -42
- package/src/superlocalmemory/evolution/triggers.py +22 -13
- package/src/superlocalmemory/graph/cozo_backend.py +43 -20
- package/src/superlocalmemory/hooks/adapter_base.py +5 -1
- package/src/superlocalmemory/hooks/auto_recall.py +13 -1
- package/src/superlocalmemory/hooks/claude_code_hooks.py +11 -0
- package/src/superlocalmemory/hooks/codex_assets.py +64 -5
- package/src/superlocalmemory/hooks/hook_daemon.py +20 -3
- package/src/superlocalmemory/hooks/memory_protocol.py +54 -0
- package/src/superlocalmemory/hooks/portable_kit.py +114 -1
- package/src/superlocalmemory/infra/backup.py +12 -1
- package/src/superlocalmemory/infra/daemon_identity.py +40 -4
- package/src/superlocalmemory/infra/data_root.py +43 -4
- package/src/superlocalmemory/infra/event_bus.py +107 -24
- package/src/superlocalmemory/infra/rate_limiter.py +93 -0
- package/src/superlocalmemory/ingestion/adapter_manager.py +4 -1
- package/src/superlocalmemory/ingestion/credentials.py +1 -1
- package/src/superlocalmemory/learning/cross_project.py +28 -19
- package/src/superlocalmemory/learning/reward_proxy.py +42 -9
- package/src/superlocalmemory/loops/__init__.py +56 -0
- package/src/superlocalmemory/loops/budget.py +58 -0
- package/src/superlocalmemory/loops/engine.py +164 -0
- package/src/superlocalmemory/loops/ledger.py +243 -0
- package/src/superlocalmemory/loops/models.py +152 -0
- package/src/superlocalmemory/loops/rules.py +52 -0
- package/src/superlocalmemory/mcp/_daemon_proxy.py +3 -0
- package/src/superlocalmemory/mcp/_pool_adapter.py +2 -0
- package/src/superlocalmemory/mcp/profiles.py +103 -0
- package/src/superlocalmemory/mcp/server.py +21 -49
- package/src/superlocalmemory/mcp/tools_active.py +4 -7
- package/src/superlocalmemory/mcp/tools_code_graph.py +51 -5
- package/src/superlocalmemory/mcp/tools_core.py +8 -1
- package/src/superlocalmemory/mcp/tools_evolution.py +6 -3
- package/src/superlocalmemory/mcp/tools_loops.py +300 -0
- package/src/superlocalmemory/mcp/tools_mesh.py +140 -4
- package/src/superlocalmemory/mcp/tools_optimize.py +15 -8
- package/src/superlocalmemory/mesh/broker.py +237 -129
- package/src/superlocalmemory/mesh/remote_sync.py +50 -8
- package/src/superlocalmemory/optimize/NOTICE +1 -6
- package/src/superlocalmemory/optimize/adapters/anthropic_adapter.py +1 -4
- package/src/superlocalmemory/optimize/adapters/openai_adapter.py +1 -4
- package/src/superlocalmemory/optimize/cache/semantic.py +27 -19
- package/src/superlocalmemory/optimize/compress/align.py +32 -26
- package/src/superlocalmemory/optimize/compress/ccr.py +14 -71
- package/src/superlocalmemory/optimize/compress/router.py +105 -22
- package/src/superlocalmemory/optimize/config/defaults.py +1 -1
- package/src/superlocalmemory/optimize/config/schema.py +87 -4
- package/src/superlocalmemory/optimize/metrics/counters.py +13 -4
- package/src/superlocalmemory/optimize/metrics/estimator.py +0 -3
- package/src/superlocalmemory/optimize/proxy/_helpers.py +31 -4
- package/src/superlocalmemory/optimize/storage/db.py +38 -9
- package/src/superlocalmemory/optimize/storage/schema.py +10 -0
- package/src/superlocalmemory/parameterization/pattern_extractor.py +6 -3
- package/src/superlocalmemory/retrieval/agentic.py +1 -1
- package/src/superlocalmemory/retrieval/bm25_channel.py +68 -10
- package/src/superlocalmemory/retrieval/engine.py +168 -26
- package/src/superlocalmemory/retrieval/entity_channel.py +7 -5
- package/src/superlocalmemory/retrieval/hopfield_channel.py +9 -2
- package/src/superlocalmemory/retrieval/semantic_channel.py +114 -21
- package/src/superlocalmemory/retrieval/spreading_activation.py +11 -2
- package/src/superlocalmemory/retrieval/temporal_channel.py +48 -9
- package/src/superlocalmemory/retrieval/temporal_frame.py +102 -0
- package/src/superlocalmemory/retrieval/temporal_validity_filter.py +135 -0
- package/src/superlocalmemory/retrieval/time_window.py +181 -0
- package/src/superlocalmemory/server/api.py +4 -4
- package/src/superlocalmemory/server/profile_runtime.py +125 -8
- package/src/superlocalmemory/server/rbac_enforce.py +142 -0
- package/src/superlocalmemory/server/recall_health.py +24 -3
- package/src/superlocalmemory/server/recall_serializer.py +19 -1
- package/src/superlocalmemory/server/routes/abstraction.py +115 -0
- package/src/superlocalmemory/server/routes/agents.py +128 -38
- package/src/superlocalmemory/server/routes/backup.py +34 -10
- package/src/superlocalmemory/server/routes/behavioral.py +13 -12
- package/src/superlocalmemory/server/routes/brain.py +21 -5
- package/src/superlocalmemory/server/routes/chat.py +10 -5
- package/src/superlocalmemory/server/routes/compliance.py +171 -21
- package/src/superlocalmemory/server/routes/config_api.py +436 -0
- package/src/superlocalmemory/server/routes/data_io.py +30 -8
- package/src/superlocalmemory/server/routes/entity.py +9 -4
- package/src/superlocalmemory/server/routes/events.py +24 -8
- package/src/superlocalmemory/server/routes/evolution.py +135 -17
- package/src/superlocalmemory/server/routes/helpers.py +16 -1
- package/src/superlocalmemory/server/routes/ingest.py +7 -4
- package/src/superlocalmemory/server/routes/insights.py +3 -3
- package/src/superlocalmemory/server/routes/learning.py +14 -14
- package/src/superlocalmemory/server/routes/lifecycle.py +59 -8
- package/src/superlocalmemory/server/routes/memories.py +182 -57
- package/src/superlocalmemory/server/routes/mesh.py +95 -15
- package/src/superlocalmemory/server/routes/optimize.py +33 -1
- package/src/superlocalmemory/server/routes/prewarm.py +2 -0
- package/src/superlocalmemory/server/routes/profiles.py +63 -17
- package/src/superlocalmemory/server/routes/ratelimit.py +124 -0
- package/src/superlocalmemory/server/routes/rbac.py +367 -0
- package/src/superlocalmemory/server/routes/stats.py +13 -6
- package/src/superlocalmemory/server/routes/tiers.py +11 -9
- package/src/superlocalmemory/server/routes/v3_api.py +183 -69
- package/src/superlocalmemory/server/routes/ws.py +5 -2
- package/src/superlocalmemory/server/security_middleware.py +12 -5
- package/src/superlocalmemory/server/ui.py +20 -5
- package/src/superlocalmemory/server/unified_daemon.py +384 -56
- package/src/superlocalmemory/server/write_identity.py +38 -8
- package/src/superlocalmemory/storage/database.py +265 -53
- package/src/superlocalmemory/storage/migration_runner.py +53 -0
- package/src/superlocalmemory/storage/migrations/M021_ingestion_log_profile.py +108 -0
- package/src/superlocalmemory/storage/migrations/M022_entity_aliases_profile.py +86 -0
- package/src/superlocalmemory/storage/migrations/M023_mesh_profile_isolation.py +194 -0
- package/src/superlocalmemory/storage/migrations/M024_rbac_users_roles.py +87 -0
- package/src/superlocalmemory/storage/migrations/M025_perf_indexes.py +90 -0
- package/src/superlocalmemory/storage/migrations/M026_rbac_memberships_fk.py +136 -0
- package/src/superlocalmemory/storage/migrations/M027_transferable_patterns_profile.py +163 -0
- package/src/superlocalmemory/storage/models.py +4 -0
- package/src/superlocalmemory/storage/schema.py +87 -0
- package/src/superlocalmemory/storage/schema_v343.py +24 -12
- package/src/superlocalmemory/trust/gate.py +49 -8
- package/src/superlocalmemory/ui/assets/slm-icon-white.svg +64 -0
- package/src/superlocalmemory/ui/assets/slm-icon.svg +36 -0
- package/src/superlocalmemory/ui/css/design-system.css +621 -0
- package/src/superlocalmemory/ui/css/neural-glass.css +6 -0
- package/src/superlocalmemory/ui/css/od-bridge.css +158 -0
- package/src/superlocalmemory/ui/favicon.svg +35 -4
- package/src/superlocalmemory/ui/index.html +306 -173
- package/src/superlocalmemory/ui/js/brain.js +5 -20
- package/src/superlocalmemory/ui/js/core.js +47 -31
- package/src/superlocalmemory/ui/js/dashboard.js +314 -63
- package/src/superlocalmemory/ui/js/event-delegation.js +102 -0
- package/src/superlocalmemory/ui/js/knowledge-graph.js +11 -11
- package/src/superlocalmemory/ui/js/math-health.js +1 -1
- package/src/superlocalmemory/ui/js/memories.js +15 -4
- package/src/superlocalmemory/ui/js/memory-chat.js +7 -7
- package/src/superlocalmemory/ui/js/ng-entities.js +6 -8
- package/src/superlocalmemory/ui/js/ng-ingestion.js +4 -4
- package/src/superlocalmemory/ui/js/ng-mesh.js +4 -9
- package/src/superlocalmemory/ui/js/ng-shell.js +8 -8
- package/src/superlocalmemory/ui/js/ng-skills.js +54 -2
- package/src/superlocalmemory/ui/js/od-agents.js +544 -0
- package/src/superlocalmemory/ui/js/od-auth-gate.js +257 -0
- package/src/superlocalmemory/ui/js/od-backup.js +780 -0
- package/src/superlocalmemory/ui/js/od-brain.js +779 -0
- package/src/superlocalmemory/ui/js/od-entities.js +579 -0
- package/src/superlocalmemory/ui/js/od-graph.js +593 -0
- package/src/superlocalmemory/ui/js/od-health.js +539 -0
- package/src/superlocalmemory/ui/js/od-mcp.js +508 -0
- package/src/superlocalmemory/ui/js/od-memories.js +887 -0
- package/src/superlocalmemory/ui/js/od-mesh.js +539 -0
- package/src/superlocalmemory/ui/js/od-operations.js +1250 -0
- package/src/superlocalmemory/ui/js/od-optimize.js +787 -0
- package/src/superlocalmemory/ui/js/od-settings.js +1053 -0
- package/src/superlocalmemory/ui/js/od-shell.js +593 -0
- package/src/superlocalmemory/ui/js/od-skills.js +573 -0
- package/src/superlocalmemory/ui/js/od-team.js +258 -0
- package/src/superlocalmemory/ui/js/profiles.js +159 -46
- package/src/superlocalmemory/ui/js/settings.js +2 -2
- package/src/superlocalmemory/ui/js/timeline.js +34 -5
- package/src/superlocalmemory/ui/js/trust-dashboard.js +2 -2
- package/src/superlocalmemory/vector/lancedb_backend.py +8 -6
- 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
|
-
|
|
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
|
-
|
|
51
|
-
|
|
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
|
|
82
|
-
logger.
|
|
83
|
-
return {"available": False, "error":
|
|
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.
|
|
99
|
-
|
|
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
|
|
107
|
-
logger.
|
|
108
|
-
return {"available": False, "error":
|
|
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
|
|
159
|
-
logger.
|
|
160
|
-
return {"success": False, "error":
|
|
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"}
|