superlocalmemory 2.8.6 → 3.0.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/LICENSE +9 -1
- package/NOTICE +63 -0
- package/README.md +165 -480
- package/bin/slm +17 -449
- package/bin/slm-npm +1 -1
- package/conftest.py +5 -0
- package/docs/api-reference.md +284 -0
- package/docs/architecture.md +149 -0
- package/docs/auto-memory.md +150 -0
- package/docs/cli-reference.md +276 -0
- package/docs/compliance.md +191 -0
- package/docs/configuration.md +182 -0
- package/docs/getting-started.md +102 -0
- package/docs/ide-setup.md +261 -0
- package/docs/mcp-tools.md +220 -0
- package/docs/migration-from-v2.md +170 -0
- package/docs/profiles.md +173 -0
- package/docs/troubleshooting.md +310 -0
- package/{configs → ide/configs}/antigravity-mcp.json +3 -3
- package/ide/configs/chatgpt-desktop-mcp.json +16 -0
- package/{configs → ide/configs}/claude-desktop-mcp.json +3 -3
- package/{configs → ide/configs}/codex-mcp.toml +4 -4
- package/{configs → ide/configs}/continue-mcp.yaml +4 -3
- package/{configs → ide/configs}/continue-skills.yaml +6 -6
- package/ide/configs/cursor-mcp.json +15 -0
- package/{configs → ide/configs}/gemini-cli-mcp.json +2 -2
- package/{configs → ide/configs}/jetbrains-mcp.json +2 -2
- package/{configs → ide/configs}/opencode-mcp.json +2 -2
- package/{configs → ide/configs}/perplexity-mcp.json +2 -2
- package/{configs → ide/configs}/vscode-copilot-mcp.json +2 -2
- package/{configs → ide/configs}/windsurf-mcp.json +3 -3
- package/{configs → ide/configs}/zed-mcp.json +2 -2
- package/{hooks → ide/hooks}/context-hook.js +9 -20
- package/ide/hooks/memory-list-skill.js +70 -0
- package/ide/hooks/memory-profile-skill.js +101 -0
- package/ide/hooks/memory-recall-skill.js +62 -0
- package/ide/hooks/memory-remember-skill.js +68 -0
- package/ide/hooks/memory-reset-skill.js +160 -0
- package/{hooks → ide/hooks}/post-recall-hook.js +2 -2
- package/ide/integrations/langchain/README.md +106 -0
- package/ide/integrations/langchain/langchain_superlocalmemory/__init__.py +9 -0
- package/ide/integrations/langchain/langchain_superlocalmemory/chat_message_history.py +201 -0
- package/ide/integrations/langchain/pyproject.toml +38 -0
- package/{src/learning → ide/integrations/langchain}/tests/__init__.py +1 -0
- package/ide/integrations/langchain/tests/test_chat_message_history.py +215 -0
- package/ide/integrations/langchain/tests/test_security.py +117 -0
- package/ide/integrations/llamaindex/README.md +81 -0
- package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/__init__.py +9 -0
- package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/base.py +316 -0
- package/ide/integrations/llamaindex/pyproject.toml +43 -0
- package/{src/lifecycle → ide/integrations/llamaindex}/tests/__init__.py +1 -2
- package/ide/integrations/llamaindex/tests/test_chat_store.py +294 -0
- package/ide/integrations/llamaindex/tests/test_security.py +241 -0
- package/{skills → ide/skills}/slm-build-graph/SKILL.md +6 -6
- package/{skills → ide/skills}/slm-list-recent/SKILL.md +5 -5
- package/{skills → ide/skills}/slm-recall/SKILL.md +5 -5
- package/{skills → ide/skills}/slm-remember/SKILL.md +6 -6
- package/{skills → ide/skills}/slm-show-patterns/SKILL.md +7 -7
- package/{skills → ide/skills}/slm-status/SKILL.md +9 -9
- package/{skills → ide/skills}/slm-switch-profile/SKILL.md +9 -9
- package/package.json +13 -22
- package/pyproject.toml +85 -0
- package/scripts/build-dmg.sh +417 -0
- package/scripts/install-skills.ps1 +334 -0
- package/scripts/postinstall.js +2 -2
- package/scripts/start-dashboard.ps1 +52 -0
- package/scripts/start-dashboard.sh +41 -0
- package/scripts/sync-wiki.ps1 +127 -0
- package/scripts/sync-wiki.sh +82 -0
- package/scripts/test-dmg.sh +161 -0
- package/scripts/test-npm-package.ps1 +252 -0
- package/scripts/test-npm-package.sh +207 -0
- package/scripts/verify-install.ps1 +294 -0
- package/scripts/verify-install.sh +266 -0
- package/src/superlocalmemory/__init__.py +0 -0
- package/src/superlocalmemory/attribution/__init__.py +9 -0
- package/src/superlocalmemory/attribution/mathematical_dna.py +235 -0
- package/src/superlocalmemory/attribution/signer.py +153 -0
- package/src/superlocalmemory/attribution/watermark.py +189 -0
- package/src/superlocalmemory/cli/__init__.py +5 -0
- package/src/superlocalmemory/cli/commands.py +245 -0
- package/src/superlocalmemory/cli/main.py +89 -0
- package/src/superlocalmemory/cli/migrate_cmd.py +55 -0
- package/src/superlocalmemory/cli/post_install.py +99 -0
- package/src/superlocalmemory/cli/setup_wizard.py +129 -0
- package/src/superlocalmemory/compliance/__init__.py +0 -0
- package/src/superlocalmemory/compliance/abac.py +204 -0
- package/src/superlocalmemory/compliance/audit.py +314 -0
- package/src/superlocalmemory/compliance/eu_ai_act.py +131 -0
- package/src/superlocalmemory/compliance/gdpr.py +294 -0
- package/src/superlocalmemory/compliance/lifecycle.py +158 -0
- package/src/superlocalmemory/compliance/retention.py +232 -0
- package/src/superlocalmemory/compliance/scheduler.py +148 -0
- package/src/superlocalmemory/core/__init__.py +0 -0
- package/src/superlocalmemory/core/config.py +391 -0
- package/src/superlocalmemory/core/embeddings.py +293 -0
- package/src/superlocalmemory/core/engine.py +701 -0
- package/src/superlocalmemory/core/hooks.py +65 -0
- package/src/superlocalmemory/core/maintenance.py +172 -0
- package/src/superlocalmemory/core/modes.py +140 -0
- package/src/superlocalmemory/core/profiles.py +234 -0
- package/src/superlocalmemory/core/registry.py +117 -0
- package/src/superlocalmemory/dynamics/__init__.py +0 -0
- package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +223 -0
- package/src/superlocalmemory/encoding/__init__.py +0 -0
- package/src/superlocalmemory/encoding/consolidator.py +485 -0
- package/src/superlocalmemory/encoding/emotional.py +125 -0
- package/src/superlocalmemory/encoding/entity_resolver.py +525 -0
- package/src/superlocalmemory/encoding/entropy_gate.py +104 -0
- package/src/superlocalmemory/encoding/fact_extractor.py +775 -0
- package/src/superlocalmemory/encoding/foresight.py +91 -0
- package/src/superlocalmemory/encoding/graph_builder.py +302 -0
- package/src/superlocalmemory/encoding/observation_builder.py +160 -0
- package/src/superlocalmemory/encoding/scene_builder.py +183 -0
- package/src/superlocalmemory/encoding/signal_inference.py +90 -0
- package/src/superlocalmemory/encoding/temporal_parser.py +426 -0
- package/src/superlocalmemory/encoding/type_router.py +235 -0
- package/src/superlocalmemory/hooks/__init__.py +3 -0
- package/src/superlocalmemory/hooks/auto_capture.py +111 -0
- package/src/superlocalmemory/hooks/auto_recall.py +93 -0
- package/src/superlocalmemory/hooks/ide_connector.py +204 -0
- package/src/superlocalmemory/hooks/rules_engine.py +99 -0
- package/src/superlocalmemory/infra/__init__.py +3 -0
- package/src/superlocalmemory/infra/auth_middleware.py +82 -0
- package/src/superlocalmemory/infra/backup.py +317 -0
- package/src/superlocalmemory/infra/cache_manager.py +267 -0
- package/src/superlocalmemory/infra/event_bus.py +381 -0
- package/src/superlocalmemory/infra/rate_limiter.py +135 -0
- package/src/{webhook_dispatcher.py → superlocalmemory/infra/webhook_dispatcher.py} +104 -101
- package/src/superlocalmemory/learning/__init__.py +0 -0
- package/src/superlocalmemory/learning/adaptive.py +172 -0
- package/src/superlocalmemory/learning/behavioral.py +490 -0
- package/src/superlocalmemory/learning/behavioral_listener.py +94 -0
- package/src/superlocalmemory/learning/bootstrap.py +298 -0
- package/src/superlocalmemory/learning/cross_project.py +399 -0
- package/src/superlocalmemory/learning/database.py +376 -0
- package/src/superlocalmemory/learning/engagement.py +323 -0
- package/src/superlocalmemory/learning/features.py +138 -0
- package/src/superlocalmemory/learning/feedback.py +316 -0
- package/src/superlocalmemory/learning/outcomes.py +255 -0
- package/src/superlocalmemory/learning/project_context.py +366 -0
- package/src/superlocalmemory/learning/ranker.py +155 -0
- package/src/superlocalmemory/learning/source_quality.py +303 -0
- package/src/superlocalmemory/learning/workflows.py +309 -0
- package/src/superlocalmemory/llm/__init__.py +0 -0
- package/src/superlocalmemory/llm/backbone.py +316 -0
- package/src/superlocalmemory/math/__init__.py +0 -0
- package/src/superlocalmemory/math/fisher.py +356 -0
- package/src/superlocalmemory/math/langevin.py +398 -0
- package/src/superlocalmemory/math/sheaf.py +257 -0
- package/src/superlocalmemory/mcp/__init__.py +0 -0
- package/src/superlocalmemory/mcp/resources.py +245 -0
- package/src/superlocalmemory/mcp/server.py +61 -0
- package/src/superlocalmemory/mcp/tools.py +18 -0
- package/src/superlocalmemory/mcp/tools_core.py +305 -0
- package/src/superlocalmemory/mcp/tools_v28.py +223 -0
- package/src/superlocalmemory/mcp/tools_v3.py +286 -0
- package/src/superlocalmemory/retrieval/__init__.py +0 -0
- package/src/superlocalmemory/retrieval/agentic.py +295 -0
- package/src/superlocalmemory/retrieval/ann_index.py +223 -0
- package/src/superlocalmemory/retrieval/bm25_channel.py +185 -0
- package/src/superlocalmemory/retrieval/bridge_discovery.py +170 -0
- package/src/superlocalmemory/retrieval/engine.py +390 -0
- package/src/superlocalmemory/retrieval/entity_channel.py +179 -0
- package/src/superlocalmemory/retrieval/fusion.py +78 -0
- package/src/superlocalmemory/retrieval/profile_channel.py +105 -0
- package/src/superlocalmemory/retrieval/reranker.py +154 -0
- package/src/superlocalmemory/retrieval/semantic_channel.py +232 -0
- package/src/superlocalmemory/retrieval/strategy.py +96 -0
- package/src/superlocalmemory/retrieval/temporal_channel.py +175 -0
- package/src/superlocalmemory/server/__init__.py +1 -0
- package/src/superlocalmemory/server/api.py +248 -0
- package/src/superlocalmemory/server/routes/__init__.py +4 -0
- package/src/superlocalmemory/server/routes/agents.py +107 -0
- package/src/superlocalmemory/server/routes/backup.py +91 -0
- package/src/superlocalmemory/server/routes/behavioral.py +127 -0
- package/src/superlocalmemory/server/routes/compliance.py +160 -0
- package/src/superlocalmemory/server/routes/data_io.py +188 -0
- package/src/superlocalmemory/server/routes/events.py +183 -0
- package/src/superlocalmemory/server/routes/helpers.py +85 -0
- package/src/superlocalmemory/server/routes/learning.py +273 -0
- package/src/superlocalmemory/server/routes/lifecycle.py +116 -0
- package/src/superlocalmemory/server/routes/memories.py +399 -0
- package/src/superlocalmemory/server/routes/profiles.py +219 -0
- package/src/superlocalmemory/server/routes/stats.py +346 -0
- package/src/superlocalmemory/server/routes/v3_api.py +365 -0
- package/src/superlocalmemory/server/routes/ws.py +82 -0
- package/src/superlocalmemory/server/security_middleware.py +57 -0
- package/src/superlocalmemory/server/ui.py +245 -0
- package/src/superlocalmemory/storage/__init__.py +0 -0
- package/src/superlocalmemory/storage/access_control.py +182 -0
- package/src/superlocalmemory/storage/database.py +594 -0
- package/src/superlocalmemory/storage/migrations.py +303 -0
- package/src/superlocalmemory/storage/models.py +406 -0
- package/src/superlocalmemory/storage/schema.py +726 -0
- package/src/superlocalmemory/storage/v2_migrator.py +317 -0
- package/src/superlocalmemory/trust/__init__.py +0 -0
- package/src/superlocalmemory/trust/gate.py +130 -0
- package/src/superlocalmemory/trust/provenance.py +124 -0
- package/src/superlocalmemory/trust/scorer.py +347 -0
- package/src/superlocalmemory/trust/signals.py +153 -0
- package/ui/index.html +278 -5
- package/ui/js/auto-settings.js +70 -0
- package/ui/js/dashboard.js +90 -0
- package/ui/js/fact-detail.js +92 -0
- package/ui/js/feedback.js +2 -2
- package/ui/js/ide-status.js +102 -0
- package/ui/js/math-health.js +98 -0
- package/ui/js/recall-lab.js +127 -0
- package/ui/js/settings.js +2 -2
- package/ui/js/trust-dashboard.js +73 -0
- package/api_server.py +0 -724
- package/bin/aider-smart +0 -72
- package/bin/superlocalmemoryv2-learning +0 -4
- package/bin/superlocalmemoryv2-list +0 -3
- package/bin/superlocalmemoryv2-patterns +0 -4
- package/bin/superlocalmemoryv2-profile +0 -3
- package/bin/superlocalmemoryv2-recall +0 -3
- package/bin/superlocalmemoryv2-remember +0 -3
- package/bin/superlocalmemoryv2-reset +0 -3
- package/bin/superlocalmemoryv2-status +0 -3
- package/configs/chatgpt-desktop-mcp.json +0 -16
- package/configs/cursor-mcp.json +0 -15
- package/hooks/memory-list-skill.js +0 -139
- package/hooks/memory-profile-skill.js +0 -273
- package/hooks/memory-recall-skill.js +0 -114
- package/hooks/memory-remember-skill.js +0 -127
- package/hooks/memory-reset-skill.js +0 -274
- package/mcp_server.py +0 -1808
- package/requirements-core.txt +0 -22
- package/requirements-learning.txt +0 -12
- package/requirements.txt +0 -12
- package/src/agent_registry.py +0 -411
- package/src/auth_middleware.py +0 -61
- package/src/auto_backup.py +0 -459
- package/src/behavioral/__init__.py +0 -49
- package/src/behavioral/behavioral_listener.py +0 -203
- package/src/behavioral/behavioral_patterns.py +0 -275
- package/src/behavioral/cross_project_transfer.py +0 -206
- package/src/behavioral/outcome_inference.py +0 -194
- package/src/behavioral/outcome_tracker.py +0 -193
- package/src/behavioral/tests/__init__.py +0 -4
- package/src/behavioral/tests/test_behavioral_integration.py +0 -108
- package/src/behavioral/tests/test_behavioral_patterns.py +0 -150
- package/src/behavioral/tests/test_cross_project_transfer.py +0 -142
- package/src/behavioral/tests/test_mcp_behavioral.py +0 -139
- package/src/behavioral/tests/test_mcp_report_outcome.py +0 -117
- package/src/behavioral/tests/test_outcome_inference.py +0 -107
- package/src/behavioral/tests/test_outcome_tracker.py +0 -96
- package/src/cache_manager.py +0 -518
- package/src/compliance/__init__.py +0 -48
- package/src/compliance/abac_engine.py +0 -149
- package/src/compliance/abac_middleware.py +0 -116
- package/src/compliance/audit_db.py +0 -215
- package/src/compliance/audit_logger.py +0 -148
- package/src/compliance/retention_manager.py +0 -289
- package/src/compliance/retention_scheduler.py +0 -186
- package/src/compliance/tests/__init__.py +0 -4
- package/src/compliance/tests/test_abac_enforcement.py +0 -95
- package/src/compliance/tests/test_abac_engine.py +0 -124
- package/src/compliance/tests/test_abac_mcp_integration.py +0 -118
- package/src/compliance/tests/test_audit_db.py +0 -123
- package/src/compliance/tests/test_audit_logger.py +0 -98
- package/src/compliance/tests/test_mcp_audit.py +0 -128
- package/src/compliance/tests/test_mcp_retention_policy.py +0 -125
- package/src/compliance/tests/test_retention_manager.py +0 -131
- package/src/compliance/tests/test_retention_scheduler.py +0 -99
- package/src/compression/__init__.py +0 -25
- package/src/compression/cli.py +0 -150
- package/src/compression/cold_storage.py +0 -217
- package/src/compression/config.py +0 -72
- package/src/compression/orchestrator.py +0 -133
- package/src/compression/tier2_compressor.py +0 -228
- package/src/compression/tier3_compressor.py +0 -153
- package/src/compression/tier_classifier.py +0 -148
- package/src/db_connection_manager.py +0 -536
- package/src/embedding_engine.py +0 -63
- package/src/embeddings/__init__.py +0 -47
- package/src/embeddings/cache.py +0 -70
- package/src/embeddings/cli.py +0 -113
- package/src/embeddings/constants.py +0 -47
- package/src/embeddings/database.py +0 -91
- package/src/embeddings/engine.py +0 -247
- package/src/embeddings/model_loader.py +0 -145
- package/src/event_bus.py +0 -562
- package/src/graph/__init__.py +0 -36
- package/src/graph/build_helpers.py +0 -74
- package/src/graph/cli.py +0 -87
- package/src/graph/cluster_builder.py +0 -188
- package/src/graph/cluster_summary.py +0 -148
- package/src/graph/constants.py +0 -47
- package/src/graph/edge_builder.py +0 -162
- package/src/graph/entity_extractor.py +0 -95
- package/src/graph/graph_core.py +0 -226
- package/src/graph/graph_search.py +0 -231
- package/src/graph/hierarchical.py +0 -207
- package/src/graph/schema.py +0 -99
- package/src/graph_engine.py +0 -52
- package/src/hnsw_index.py +0 -628
- package/src/hybrid_search.py +0 -46
- package/src/learning/__init__.py +0 -217
- package/src/learning/adaptive_ranker.py +0 -682
- package/src/learning/bootstrap/__init__.py +0 -69
- package/src/learning/bootstrap/constants.py +0 -93
- package/src/learning/bootstrap/db_queries.py +0 -316
- package/src/learning/bootstrap/sampling.py +0 -82
- package/src/learning/bootstrap/text_utils.py +0 -71
- package/src/learning/cross_project_aggregator.py +0 -857
- package/src/learning/db/__init__.py +0 -40
- package/src/learning/db/constants.py +0 -44
- package/src/learning/db/schema.py +0 -279
- package/src/learning/engagement_tracker.py +0 -628
- package/src/learning/feature_extractor.py +0 -708
- package/src/learning/feedback_collector.py +0 -806
- package/src/learning/learning_db.py +0 -915
- package/src/learning/project_context_manager.py +0 -572
- package/src/learning/ranking/__init__.py +0 -33
- package/src/learning/ranking/constants.py +0 -84
- package/src/learning/ranking/helpers.py +0 -278
- package/src/learning/source_quality_scorer.py +0 -676
- package/src/learning/synthetic_bootstrap.py +0 -755
- package/src/learning/tests/test_adaptive_ranker.py +0 -325
- package/src/learning/tests/test_adaptive_ranker_v28.py +0 -60
- package/src/learning/tests/test_aggregator.py +0 -306
- package/src/learning/tests/test_auto_retrain_v28.py +0 -35
- package/src/learning/tests/test_e2e_ranking_v28.py +0 -82
- package/src/learning/tests/test_feature_extractor_v28.py +0 -93
- package/src/learning/tests/test_feedback_collector.py +0 -294
- package/src/learning/tests/test_learning_db.py +0 -602
- package/src/learning/tests/test_learning_db_v28.py +0 -110
- package/src/learning/tests/test_learning_init_v28.py +0 -48
- package/src/learning/tests/test_outcome_signals.py +0 -48
- package/src/learning/tests/test_project_context.py +0 -292
- package/src/learning/tests/test_schema_migration.py +0 -319
- package/src/learning/tests/test_signal_inference.py +0 -397
- package/src/learning/tests/test_source_quality.py +0 -351
- package/src/learning/tests/test_synthetic_bootstrap.py +0 -429
- package/src/learning/tests/test_workflow_miner.py +0 -318
- package/src/learning/workflow_pattern_miner.py +0 -655
- package/src/lifecycle/__init__.py +0 -54
- package/src/lifecycle/bounded_growth.py +0 -239
- package/src/lifecycle/compaction_engine.py +0 -226
- package/src/lifecycle/lifecycle_engine.py +0 -355
- package/src/lifecycle/lifecycle_evaluator.py +0 -257
- package/src/lifecycle/lifecycle_scheduler.py +0 -130
- package/src/lifecycle/retention_policy.py +0 -285
- package/src/lifecycle/tests/test_bounded_growth.py +0 -193
- package/src/lifecycle/tests/test_compaction.py +0 -179
- package/src/lifecycle/tests/test_lifecycle_engine.py +0 -137
- package/src/lifecycle/tests/test_lifecycle_evaluation.py +0 -177
- package/src/lifecycle/tests/test_lifecycle_scheduler.py +0 -127
- package/src/lifecycle/tests/test_lifecycle_search.py +0 -109
- package/src/lifecycle/tests/test_mcp_compact.py +0 -149
- package/src/lifecycle/tests/test_mcp_lifecycle_status.py +0 -114
- package/src/lifecycle/tests/test_retention_policy.py +0 -162
- package/src/mcp_tools_v28.py +0 -281
- package/src/memory/__init__.py +0 -36
- package/src/memory/cli.py +0 -205
- package/src/memory/constants.py +0 -39
- package/src/memory/helpers.py +0 -28
- package/src/memory/schema.py +0 -166
- package/src/memory-profiles.py +0 -595
- package/src/memory-reset.py +0 -491
- package/src/memory_compression.py +0 -989
- package/src/memory_store_v2.py +0 -1155
- package/src/migrate_v1_to_v2.py +0 -629
- package/src/pattern_learner.py +0 -34
- package/src/patterns/__init__.py +0 -24
- package/src/patterns/analyzers.py +0 -251
- package/src/patterns/learner.py +0 -271
- package/src/patterns/scoring.py +0 -171
- package/src/patterns/store.py +0 -225
- package/src/patterns/terminology.py +0 -140
- package/src/provenance_tracker.py +0 -312
- package/src/qualixar_attribution.py +0 -139
- package/src/qualixar_watermark.py +0 -78
- package/src/query_optimizer.py +0 -511
- package/src/rate_limiter.py +0 -83
- package/src/search/__init__.py +0 -20
- package/src/search/cli.py +0 -77
- package/src/search/constants.py +0 -26
- package/src/search/engine.py +0 -241
- package/src/search/fusion.py +0 -122
- package/src/search/index_loader.py +0 -114
- package/src/search/methods.py +0 -162
- package/src/search_engine_v2.py +0 -401
- package/src/setup_validator.py +0 -482
- package/src/subscription_manager.py +0 -391
- package/src/tree/__init__.py +0 -59
- package/src/tree/builder.py +0 -185
- package/src/tree/nodes.py +0 -202
- package/src/tree/queries.py +0 -257
- package/src/tree/schema.py +0 -80
- package/src/tree_manager.py +0 -19
- package/src/trust/__init__.py +0 -45
- package/src/trust/constants.py +0 -66
- package/src/trust/queries.py +0 -157
- package/src/trust/schema.py +0 -95
- package/src/trust/scorer.py +0 -299
- package/src/trust/signals.py +0 -95
- package/src/trust_scorer.py +0 -44
- package/ui/app.js +0 -1588
- package/ui/js/graph-cytoscape-monolithic-backup.js +0 -1168
- package/ui/js/graph-cytoscape.js +0 -1168
- package/ui/js/graph-d3-backup.js +0 -32
- package/ui/js/graph.js +0 -32
- package/ui_server.py +0 -286
- /package/docs/{ACCESSIBILITY.md → v2-archive/ACCESSIBILITY.md} +0 -0
- /package/docs/{ARCHITECTURE.md → v2-archive/ARCHITECTURE.md} +0 -0
- /package/docs/{CLI-COMMANDS-REFERENCE.md → v2-archive/CLI-COMMANDS-REFERENCE.md} +0 -0
- /package/docs/{COMPRESSION-README.md → v2-archive/COMPRESSION-README.md} +0 -0
- /package/docs/{FRAMEWORK-INTEGRATIONS.md → v2-archive/FRAMEWORK-INTEGRATIONS.md} +0 -0
- /package/docs/{MCP-MANUAL-SETUP.md → v2-archive/MCP-MANUAL-SETUP.md} +0 -0
- /package/docs/{MCP-TROUBLESHOOTING.md → v2-archive/MCP-TROUBLESHOOTING.md} +0 -0
- /package/docs/{PATTERN-LEARNING.md → v2-archive/PATTERN-LEARNING.md} +0 -0
- /package/docs/{PROFILES-GUIDE.md → v2-archive/PROFILES-GUIDE.md} +0 -0
- /package/docs/{RESET-GUIDE.md → v2-archive/RESET-GUIDE.md} +0 -0
- /package/docs/{SEARCH-ENGINE-V2.2.0.md → v2-archive/SEARCH-ENGINE-V2.2.0.md} +0 -0
- /package/docs/{SEARCH-INTEGRATION-GUIDE.md → v2-archive/SEARCH-INTEGRATION-GUIDE.md} +0 -0
- /package/docs/{UI-SERVER.md → v2-archive/UI-SERVER.md} +0 -0
- /package/docs/{UNIVERSAL-INTEGRATION.md → v2-archive/UNIVERSAL-INTEGRATION.md} +0 -0
- /package/docs/{V2.2.0-OPTIONAL-SEARCH.md → v2-archive/V2.2.0-OPTIONAL-SEARCH.md} +0 -0
- /package/docs/{WINDOWS-INSTALL-README.txt → v2-archive/WINDOWS-INSTALL-README.txt} +0 -0
- /package/docs/{WINDOWS-POST-INSTALL.txt → v2-archive/WINDOWS-POST-INSTALL.txt} +0 -0
- /package/docs/{example_graph_usage.py → v2-archive/example_graph_usage.py} +0 -0
- /package/{completions → ide/completions}/slm.bash +0 -0
- /package/{completions → ide/completions}/slm.zsh +0 -0
- /package/{configs → ide/configs}/cody-commands.json +0 -0
- /package/{install-skills.sh → scripts/install-skills.sh} +0 -0
- /package/{install.ps1 → scripts/install.ps1} +0 -0
- /package/{install.sh → scripts/install.sh} +0 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
|
+
# Licensed under the MIT License - see LICENSE file
|
|
3
|
+
# Part of SuperLocalMemory V3 | https://qualixar.com | https://varunpratap.com
|
|
4
|
+
|
|
5
|
+
"""Attribute-Based Access Control for memory operations.
|
|
6
|
+
|
|
7
|
+
Evaluates policies: (agent_id, profile_id, action) -> allow/deny.
|
|
8
|
+
Default: allow all (open access). Policies restrict specific agents.
|
|
9
|
+
|
|
10
|
+
Actions: "read", "write", "delete", "admin".
|
|
11
|
+
Policies are stored in-memory (loaded from DB on init).
|
|
12
|
+
Simple deny-list approach: if a deny policy matches, access is blocked.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import logging
|
|
18
|
+
import sqlite3
|
|
19
|
+
from typing import Any, Optional
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
# Valid ABAC actions
|
|
24
|
+
VALID_ACTIONS = frozenset({"read", "write", "delete", "admin"})
|
|
25
|
+
|
|
26
|
+
_POLICY_TABLE_SQL = """
|
|
27
|
+
CREATE TABLE IF NOT EXISTS abac_policies (
|
|
28
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
29
|
+
profile_id TEXT NOT NULL,
|
|
30
|
+
agent_id TEXT NOT NULL,
|
|
31
|
+
action TEXT NOT NULL,
|
|
32
|
+
deny INTEGER NOT NULL DEFAULT 1,
|
|
33
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
34
|
+
)
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class AccessDenied(PermissionError):
|
|
39
|
+
"""Raised when ABAC denies an operation."""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class ABACEngine:
|
|
43
|
+
"""Attribute-Based Access Control for memory operations.
|
|
44
|
+
|
|
45
|
+
Evaluates policies: (agent_id, profile_id, action) -> allow/deny.
|
|
46
|
+
Default: allow all (open access). Deny policies restrict specific
|
|
47
|
+
agent+profile+action combinations.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def __init__(self, db: Optional[sqlite3.Connection] = None) -> None:
|
|
51
|
+
self._policies: list[dict[str, Any]] = []
|
|
52
|
+
self._db = db
|
|
53
|
+
if db is not None:
|
|
54
|
+
self._load_policies_from_db(db)
|
|
55
|
+
|
|
56
|
+
# ------------------------------------------------------------------
|
|
57
|
+
# Policy loading
|
|
58
|
+
# ------------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
def _load_policies_from_db(self, db: sqlite3.Connection) -> None:
|
|
61
|
+
"""Load policies from the abac_policies table."""
|
|
62
|
+
try:
|
|
63
|
+
db.execute(_POLICY_TABLE_SQL)
|
|
64
|
+
db.commit()
|
|
65
|
+
rows = db.execute(
|
|
66
|
+
"SELECT profile_id, agent_id, action, deny "
|
|
67
|
+
"FROM abac_policies"
|
|
68
|
+
).fetchall()
|
|
69
|
+
for row in rows:
|
|
70
|
+
self._policies.append({
|
|
71
|
+
"profile_id": row[0],
|
|
72
|
+
"agent_id": row[1],
|
|
73
|
+
"action": row[2],
|
|
74
|
+
"deny": bool(row[3]),
|
|
75
|
+
})
|
|
76
|
+
logger.info("Loaded %d ABAC policies from DB", len(self._policies))
|
|
77
|
+
except sqlite3.OperationalError as exc:
|
|
78
|
+
logger.warning("Failed to load ABAC policies: %s", exc)
|
|
79
|
+
|
|
80
|
+
# ------------------------------------------------------------------
|
|
81
|
+
# Policy management
|
|
82
|
+
# ------------------------------------------------------------------
|
|
83
|
+
|
|
84
|
+
def add_policy(
|
|
85
|
+
self,
|
|
86
|
+
profile_id: str,
|
|
87
|
+
agent_id: str,
|
|
88
|
+
action: str,
|
|
89
|
+
deny: bool = True,
|
|
90
|
+
) -> None:
|
|
91
|
+
"""Add an access control policy.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
profile_id: Profile this policy applies to.
|
|
95
|
+
agent_id: Agent this policy applies to.
|
|
96
|
+
action: The action to control (read/write/delete/admin).
|
|
97
|
+
deny: If True, this is a deny policy. Default True.
|
|
98
|
+
"""
|
|
99
|
+
if action not in VALID_ACTIONS:
|
|
100
|
+
raise ValueError(f"Invalid action '{action}'. Must be one of {VALID_ACTIONS}")
|
|
101
|
+
policy = {
|
|
102
|
+
"profile_id": profile_id,
|
|
103
|
+
"agent_id": agent_id,
|
|
104
|
+
"action": action,
|
|
105
|
+
"deny": deny,
|
|
106
|
+
}
|
|
107
|
+
self._policies.append(policy)
|
|
108
|
+
self._persist_policy(policy)
|
|
109
|
+
|
|
110
|
+
def remove_policy(
|
|
111
|
+
self,
|
|
112
|
+
profile_id: str,
|
|
113
|
+
agent_id: str,
|
|
114
|
+
action: str,
|
|
115
|
+
) -> None:
|
|
116
|
+
"""Remove a policy matching profile_id + agent_id + action."""
|
|
117
|
+
self._policies = [
|
|
118
|
+
p for p in self._policies
|
|
119
|
+
if not (
|
|
120
|
+
p["profile_id"] == profile_id
|
|
121
|
+
and p["agent_id"] == agent_id
|
|
122
|
+
and p["action"] == action
|
|
123
|
+
)
|
|
124
|
+
]
|
|
125
|
+
if self._db is not None:
|
|
126
|
+
try:
|
|
127
|
+
self._db.execute(
|
|
128
|
+
"DELETE FROM abac_policies WHERE profile_id = ? AND agent_id = ? AND action = ?",
|
|
129
|
+
(profile_id, agent_id, action),
|
|
130
|
+
)
|
|
131
|
+
self._db.commit()
|
|
132
|
+
except sqlite3.OperationalError:
|
|
133
|
+
pass
|
|
134
|
+
|
|
135
|
+
def _persist_policy(self, policy: dict[str, Any]) -> None:
|
|
136
|
+
"""Write a policy to DB so it survives restart."""
|
|
137
|
+
if self._db is None:
|
|
138
|
+
return
|
|
139
|
+
try:
|
|
140
|
+
self._db.execute(
|
|
141
|
+
"INSERT INTO abac_policies (profile_id, agent_id, action, deny) "
|
|
142
|
+
"VALUES (?, ?, ?, ?)",
|
|
143
|
+
(policy["profile_id"], policy["agent_id"], policy["action"], int(policy["deny"])),
|
|
144
|
+
)
|
|
145
|
+
self._db.commit()
|
|
146
|
+
except sqlite3.OperationalError as exc:
|
|
147
|
+
logger.warning("Failed to persist ABAC policy: %s", exc)
|
|
148
|
+
|
|
149
|
+
# ------------------------------------------------------------------
|
|
150
|
+
# Access evaluation
|
|
151
|
+
# ------------------------------------------------------------------
|
|
152
|
+
|
|
153
|
+
def check(self, agent_id: str, profile_id: str, action: str) -> bool:
|
|
154
|
+
"""Evaluate access. Returns True if allowed, False if denied.
|
|
155
|
+
|
|
156
|
+
Default: allow if no deny policy matches the request.
|
|
157
|
+
Deny-first semantics: any matching deny policy blocks access.
|
|
158
|
+
"""
|
|
159
|
+
for policy in self._policies:
|
|
160
|
+
if not policy.get("deny", True):
|
|
161
|
+
continue
|
|
162
|
+
if (
|
|
163
|
+
policy["agent_id"] == agent_id
|
|
164
|
+
and policy["profile_id"] == profile_id
|
|
165
|
+
and policy["action"] == action
|
|
166
|
+
):
|
|
167
|
+
return False
|
|
168
|
+
return True
|
|
169
|
+
|
|
170
|
+
def check_or_raise(
|
|
171
|
+
self,
|
|
172
|
+
agent_id: str,
|
|
173
|
+
profile_id: str,
|
|
174
|
+
action: str,
|
|
175
|
+
) -> None:
|
|
176
|
+
"""Like check() but raises AccessDenied if denied."""
|
|
177
|
+
if not self.check(agent_id, profile_id, action):
|
|
178
|
+
raise AccessDenied(
|
|
179
|
+
f"Agent '{agent_id}' denied '{action}' "
|
|
180
|
+
f"on profile '{profile_id}'"
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
# ------------------------------------------------------------------
|
|
184
|
+
# Policy listing
|
|
185
|
+
# ------------------------------------------------------------------
|
|
186
|
+
|
|
187
|
+
def list_policies(
|
|
188
|
+
self,
|
|
189
|
+
profile_id: Optional[str] = None,
|
|
190
|
+
) -> list[dict[str, Any]]:
|
|
191
|
+
"""List all policies, optionally filtered by profile.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
profile_id: If provided, only return policies for this profile.
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
List of policy dicts with keys: profile_id, agent_id, action, deny.
|
|
198
|
+
"""
|
|
199
|
+
if profile_id is None:
|
|
200
|
+
return [dict(p) for p in self._policies]
|
|
201
|
+
return [
|
|
202
|
+
dict(p) for p in self._policies
|
|
203
|
+
if p["profile_id"] == profile_id
|
|
204
|
+
]
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
|
+
# Licensed under the MIT License - see LICENSE file
|
|
3
|
+
# Part of SuperLocalMemory V3 | https://qualixar.com | https://varunpratap.com
|
|
4
|
+
|
|
5
|
+
"""Tamper-proof hash-chain audit log for compliance.
|
|
6
|
+
|
|
7
|
+
Every operation is logged with a SHA-256 hash that includes the previous
|
|
8
|
+
entry's hash, creating a chain. Tampering with any entry breaks the chain
|
|
9
|
+
and is detectable via verify_integrity().
|
|
10
|
+
|
|
11
|
+
The audit chain uses its OWN sqlite3 connection (not shared DB manager)
|
|
12
|
+
for independence — audit must survive even if the main DB is corrupted.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import hashlib
|
|
18
|
+
import json
|
|
19
|
+
import logging
|
|
20
|
+
import sqlite3
|
|
21
|
+
import threading
|
|
22
|
+
from datetime import datetime, timezone
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from typing import Any, Optional, Union
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
_GENESIS_HASH = "genesis"
|
|
29
|
+
|
|
30
|
+
_SCHEMA = """
|
|
31
|
+
CREATE TABLE IF NOT EXISTS audit_chain (
|
|
32
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
33
|
+
timestamp TEXT NOT NULL,
|
|
34
|
+
operation TEXT NOT NULL,
|
|
35
|
+
agent_id TEXT DEFAULT '',
|
|
36
|
+
profile_id TEXT DEFAULT '',
|
|
37
|
+
content_hash TEXT DEFAULT '',
|
|
38
|
+
prev_hash TEXT DEFAULT '',
|
|
39
|
+
event_hash TEXT NOT NULL,
|
|
40
|
+
metadata TEXT DEFAULT '{}'
|
|
41
|
+
);
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _compute_hash(
|
|
46
|
+
prev_hash: str,
|
|
47
|
+
operation: str,
|
|
48
|
+
agent_id: str,
|
|
49
|
+
profile_id: str,
|
|
50
|
+
content_hash: str,
|
|
51
|
+
timestamp: str,
|
|
52
|
+
) -> str:
|
|
53
|
+
"""Compute SHA-256 hash for an audit entry.
|
|
54
|
+
|
|
55
|
+
The hash incorporates: prev_hash + operation + agent_id +
|
|
56
|
+
profile_id + content_hash + timestamp.
|
|
57
|
+
"""
|
|
58
|
+
payload = (
|
|
59
|
+
f"{prev_hash}{operation}{agent_id}"
|
|
60
|
+
f"{profile_id}{content_hash}{timestamp}"
|
|
61
|
+
)
|
|
62
|
+
return hashlib.sha256(payload.encode("utf-8")).hexdigest()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class AuditChain:
|
|
66
|
+
"""Tamper-proof hash-chain audit log for compliance.
|
|
67
|
+
|
|
68
|
+
Every operation is logged with a hash that includes the previous hash,
|
|
69
|
+
creating a chain. Tampering with any entry breaks the chain.
|
|
70
|
+
|
|
71
|
+
Uses its own SQLite connection for independence from main DB.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
def __init__(self, db_path: Optional[Union[str, Path]] = None) -> None:
|
|
75
|
+
self._db_path = str(db_path) if db_path else ":memory:"
|
|
76
|
+
self._is_memory = self._db_path == ":memory:"
|
|
77
|
+
self._lock = threading.Lock()
|
|
78
|
+
# For in-memory DBs, keep a persistent connection (each connect()
|
|
79
|
+
# to ":memory:" creates a separate empty database).
|
|
80
|
+
self._persistent_conn: Optional[sqlite3.Connection] = None
|
|
81
|
+
if self._is_memory:
|
|
82
|
+
self._persistent_conn = self._make_conn()
|
|
83
|
+
self._init_db()
|
|
84
|
+
|
|
85
|
+
# ------------------------------------------------------------------
|
|
86
|
+
# Internal helpers
|
|
87
|
+
# ------------------------------------------------------------------
|
|
88
|
+
|
|
89
|
+
@staticmethod
|
|
90
|
+
def _make_conn_from_path(path: str) -> sqlite3.Connection:
|
|
91
|
+
"""Create a configured SQLite connection."""
|
|
92
|
+
conn = sqlite3.connect(path)
|
|
93
|
+
conn.execute("PRAGMA journal_mode=WAL")
|
|
94
|
+
conn.row_factory = sqlite3.Row
|
|
95
|
+
return conn
|
|
96
|
+
|
|
97
|
+
def _make_conn(self) -> sqlite3.Connection:
|
|
98
|
+
"""Create a new configured connection to the audit database."""
|
|
99
|
+
return self._make_conn_from_path(self._db_path)
|
|
100
|
+
|
|
101
|
+
def _get_conn(self) -> sqlite3.Connection:
|
|
102
|
+
"""Get a connection to the audit database.
|
|
103
|
+
|
|
104
|
+
For in-memory databases, returns the persistent connection.
|
|
105
|
+
For file databases, creates a new connection each time.
|
|
106
|
+
"""
|
|
107
|
+
if self._is_memory and self._persistent_conn is not None:
|
|
108
|
+
return self._persistent_conn
|
|
109
|
+
return self._make_conn()
|
|
110
|
+
|
|
111
|
+
def _release_conn(self, conn: sqlite3.Connection) -> None:
|
|
112
|
+
"""Release a connection. Only closes file-based connections."""
|
|
113
|
+
if not self._is_memory:
|
|
114
|
+
conn.close()
|
|
115
|
+
|
|
116
|
+
def _init_db(self) -> None:
|
|
117
|
+
"""Initialize the audit_chain table."""
|
|
118
|
+
conn = self._get_conn()
|
|
119
|
+
try:
|
|
120
|
+
conn.executescript(_SCHEMA)
|
|
121
|
+
conn.commit()
|
|
122
|
+
finally:
|
|
123
|
+
self._release_conn(conn)
|
|
124
|
+
|
|
125
|
+
def _get_last_hash(self, conn: sqlite3.Connection) -> str:
|
|
126
|
+
"""Get the hash of the most recent entry, or genesis."""
|
|
127
|
+
row = conn.execute(
|
|
128
|
+
"SELECT event_hash FROM audit_chain "
|
|
129
|
+
"ORDER BY id DESC LIMIT 1"
|
|
130
|
+
).fetchone()
|
|
131
|
+
return row["event_hash"] if row else _GENESIS_HASH
|
|
132
|
+
|
|
133
|
+
# ------------------------------------------------------------------
|
|
134
|
+
# Public API
|
|
135
|
+
# ------------------------------------------------------------------
|
|
136
|
+
|
|
137
|
+
def log(
|
|
138
|
+
self,
|
|
139
|
+
operation: str,
|
|
140
|
+
agent_id: str = "",
|
|
141
|
+
profile_id: str = "",
|
|
142
|
+
content_hash: str = "",
|
|
143
|
+
metadata: Optional[dict[str, Any]] = None,
|
|
144
|
+
) -> str:
|
|
145
|
+
"""Log an audit event. Returns the event hash.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
operation: Type of operation (store, recall, delete, etc.).
|
|
149
|
+
agent_id: ID of the agent performing the operation.
|
|
150
|
+
profile_id: ID of the profile being accessed.
|
|
151
|
+
content_hash: Hash of the content involved (optional).
|
|
152
|
+
metadata: Additional metadata dict (optional).
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
The SHA-256 event hash for this entry.
|
|
156
|
+
"""
|
|
157
|
+
ts = datetime.now(timezone.utc).isoformat()
|
|
158
|
+
metadata_str = json.dumps(metadata or {}, sort_keys=True)
|
|
159
|
+
|
|
160
|
+
with self._lock:
|
|
161
|
+
conn = self._get_conn()
|
|
162
|
+
try:
|
|
163
|
+
prev_hash = self._get_last_hash(conn)
|
|
164
|
+
event_hash = _compute_hash(
|
|
165
|
+
prev_hash, operation, agent_id,
|
|
166
|
+
profile_id, content_hash, ts,
|
|
167
|
+
)
|
|
168
|
+
conn.execute(
|
|
169
|
+
"INSERT INTO audit_chain "
|
|
170
|
+
"(timestamp, operation, agent_id, profile_id, "
|
|
171
|
+
" content_hash, prev_hash, event_hash, metadata) "
|
|
172
|
+
"VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
|
173
|
+
(
|
|
174
|
+
ts, operation, agent_id, profile_id,
|
|
175
|
+
content_hash, prev_hash, event_hash, metadata_str,
|
|
176
|
+
),
|
|
177
|
+
)
|
|
178
|
+
conn.commit()
|
|
179
|
+
return event_hash
|
|
180
|
+
finally:
|
|
181
|
+
self._release_conn(conn)
|
|
182
|
+
|
|
183
|
+
def query(
|
|
184
|
+
self,
|
|
185
|
+
profile_id: Optional[str] = None,
|
|
186
|
+
agent_id: Optional[str] = None,
|
|
187
|
+
operation: Optional[str] = None,
|
|
188
|
+
start_date: Optional[str] = None,
|
|
189
|
+
end_date: Optional[str] = None,
|
|
190
|
+
limit: int = 100,
|
|
191
|
+
) -> list[dict[str, Any]]:
|
|
192
|
+
"""Query audit events with filters.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
profile_id: Filter by profile ID.
|
|
196
|
+
agent_id: Filter by agent ID.
|
|
197
|
+
operation: Filter by operation type.
|
|
198
|
+
start_date: ISO date string for range start.
|
|
199
|
+
end_date: ISO date string for range end.
|
|
200
|
+
limit: Maximum number of events to return.
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
List of event dicts ordered by id descending.
|
|
204
|
+
"""
|
|
205
|
+
clauses: list[str] = []
|
|
206
|
+
params: list[Any] = []
|
|
207
|
+
|
|
208
|
+
if profile_id is not None:
|
|
209
|
+
clauses.append("profile_id = ?")
|
|
210
|
+
params.append(profile_id)
|
|
211
|
+
if agent_id is not None:
|
|
212
|
+
clauses.append("agent_id = ?")
|
|
213
|
+
params.append(agent_id)
|
|
214
|
+
if operation is not None:
|
|
215
|
+
clauses.append("operation = ?")
|
|
216
|
+
params.append(operation)
|
|
217
|
+
if start_date is not None:
|
|
218
|
+
clauses.append("timestamp >= ?")
|
|
219
|
+
params.append(start_date)
|
|
220
|
+
if end_date is not None:
|
|
221
|
+
clauses.append("timestamp <= ?")
|
|
222
|
+
params.append(end_date)
|
|
223
|
+
|
|
224
|
+
where = f"WHERE {' AND '.join(clauses)}" if clauses else ""
|
|
225
|
+
sql = (
|
|
226
|
+
f"SELECT id, timestamp, operation, agent_id, profile_id, "
|
|
227
|
+
f"content_hash, prev_hash, event_hash, metadata "
|
|
228
|
+
f"FROM audit_chain {where} "
|
|
229
|
+
f"ORDER BY id DESC LIMIT ?"
|
|
230
|
+
)
|
|
231
|
+
params.append(limit)
|
|
232
|
+
|
|
233
|
+
with self._lock:
|
|
234
|
+
conn = self._get_conn()
|
|
235
|
+
try:
|
|
236
|
+
rows = conn.execute(sql, params).fetchall()
|
|
237
|
+
return [dict(r) for r in rows]
|
|
238
|
+
finally:
|
|
239
|
+
self._release_conn(conn)
|
|
240
|
+
|
|
241
|
+
def verify_integrity(self) -> bool:
|
|
242
|
+
"""Verify the entire hash chain. Returns True if chain is intact.
|
|
243
|
+
|
|
244
|
+
Walks every entry in order, recomputes each hash, and verifies
|
|
245
|
+
it matches the stored hash. Any mismatch means tampering.
|
|
246
|
+
"""
|
|
247
|
+
conn = self._get_conn()
|
|
248
|
+
try:
|
|
249
|
+
rows = conn.execute(
|
|
250
|
+
"SELECT id, timestamp, operation, agent_id, profile_id, "
|
|
251
|
+
"content_hash, prev_hash, event_hash "
|
|
252
|
+
"FROM audit_chain ORDER BY id"
|
|
253
|
+
).fetchall()
|
|
254
|
+
finally:
|
|
255
|
+
self._release_conn(conn)
|
|
256
|
+
|
|
257
|
+
if not rows:
|
|
258
|
+
return True
|
|
259
|
+
|
|
260
|
+
expected_prev = _GENESIS_HASH
|
|
261
|
+
for row in rows:
|
|
262
|
+
row_dict = dict(row)
|
|
263
|
+
|
|
264
|
+
# Check the prev_hash link
|
|
265
|
+
if row_dict["prev_hash"] != expected_prev:
|
|
266
|
+
logger.warning(
|
|
267
|
+
"Audit chain prev_hash mismatch at entry %d",
|
|
268
|
+
row_dict["id"],
|
|
269
|
+
)
|
|
270
|
+
return False
|
|
271
|
+
|
|
272
|
+
# Recompute the entry hash
|
|
273
|
+
computed = _compute_hash(
|
|
274
|
+
row_dict["prev_hash"],
|
|
275
|
+
row_dict["operation"],
|
|
276
|
+
row_dict["agent_id"],
|
|
277
|
+
row_dict["profile_id"],
|
|
278
|
+
row_dict["content_hash"],
|
|
279
|
+
row_dict["timestamp"],
|
|
280
|
+
)
|
|
281
|
+
if computed != row_dict["event_hash"]:
|
|
282
|
+
logger.warning(
|
|
283
|
+
"Audit chain event_hash mismatch at entry %d",
|
|
284
|
+
row_dict["id"],
|
|
285
|
+
)
|
|
286
|
+
return False
|
|
287
|
+
|
|
288
|
+
expected_prev = row_dict["event_hash"]
|
|
289
|
+
|
|
290
|
+
return True
|
|
291
|
+
|
|
292
|
+
def get_stats(self) -> dict[str, int]:
|
|
293
|
+
"""Get audit statistics (event counts by operation type).
|
|
294
|
+
|
|
295
|
+
Returns:
|
|
296
|
+
Dict mapping operation names to their counts, plus
|
|
297
|
+
a 'total' key with the chain length.
|
|
298
|
+
"""
|
|
299
|
+
conn = self._get_conn()
|
|
300
|
+
try:
|
|
301
|
+
rows = conn.execute(
|
|
302
|
+
"SELECT operation, COUNT(*) AS cnt "
|
|
303
|
+
"FROM audit_chain GROUP BY operation"
|
|
304
|
+
).fetchall()
|
|
305
|
+
stats: dict[str, int] = {}
|
|
306
|
+
total = 0
|
|
307
|
+
for row in rows:
|
|
308
|
+
count = row["cnt"]
|
|
309
|
+
stats[row["operation"]] = count
|
|
310
|
+
total += count
|
|
311
|
+
stats["total"] = total
|
|
312
|
+
return stats
|
|
313
|
+
finally:
|
|
314
|
+
self._release_conn(conn)
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
|
+
# Licensed under the MIT License - see LICENSE file
|
|
3
|
+
# Part of SuperLocalMemory V3 | https://qualixar.com | https://varunpratap.com
|
|
4
|
+
|
|
5
|
+
"""SuperLocalMemory V3 — EU AI Act Compliance Verification.
|
|
6
|
+
|
|
7
|
+
Verifies that each operating mode meets EU AI Act requirements.
|
|
8
|
+
Mode A and B: FULL compliance (zero cloud, zero generative AI / local only).
|
|
9
|
+
Mode C: NOT compliant (cloud LLM processing).
|
|
10
|
+
|
|
11
|
+
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import logging
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
from datetime import UTC, datetime
|
|
19
|
+
|
|
20
|
+
from superlocalmemory.core.modes import get_capabilities
|
|
21
|
+
from superlocalmemory.storage.models import Mode
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass(frozen=True)
|
|
27
|
+
class ComplianceReport:
|
|
28
|
+
"""EU AI Act compliance assessment for a specific mode."""
|
|
29
|
+
|
|
30
|
+
mode: Mode
|
|
31
|
+
compliant: bool
|
|
32
|
+
risk_category: str # "minimal" / "limited" / "high" / "unacceptable"
|
|
33
|
+
data_stays_local: bool
|
|
34
|
+
uses_generative_ai: bool
|
|
35
|
+
transparency_met: bool
|
|
36
|
+
human_oversight: bool
|
|
37
|
+
findings: list[str]
|
|
38
|
+
timestamp: str
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class EUAIActChecker:
|
|
42
|
+
"""Verify EU AI Act compliance for each operating mode.
|
|
43
|
+
|
|
44
|
+
EU AI Act (effective Aug 2025) classifies AI systems by risk:
|
|
45
|
+
- Minimal risk: No obligations (most AI systems)
|
|
46
|
+
- Limited risk: Transparency obligations
|
|
47
|
+
- High risk: Strict requirements (biometric, critical infra)
|
|
48
|
+
- Unacceptable: Banned
|
|
49
|
+
|
|
50
|
+
Memory systems are generally "minimal risk" UNLESS they process
|
|
51
|
+
personal data via cloud AI services (then "limited risk" with
|
|
52
|
+
transparency obligations).
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def check_compliance(self, mode: Mode) -> ComplianceReport:
|
|
56
|
+
"""Generate compliance report for a mode."""
|
|
57
|
+
caps = get_capabilities(mode)
|
|
58
|
+
findings: list[str] = []
|
|
59
|
+
|
|
60
|
+
# Data locality
|
|
61
|
+
data_local = caps.data_stays_local
|
|
62
|
+
if not data_local:
|
|
63
|
+
findings.append(
|
|
64
|
+
"Data leaves device for cloud LLM processing. "
|
|
65
|
+
"Requires Data Processing Agreement (DPA) with provider."
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# Generative AI usage
|
|
69
|
+
uses_gen_ai = caps.llm_fact_extraction or caps.llm_answer_generation
|
|
70
|
+
local_gen_ai = uses_gen_ai and data_local
|
|
71
|
+
|
|
72
|
+
if uses_gen_ai and not data_local:
|
|
73
|
+
findings.append(
|
|
74
|
+
"Uses cloud generative AI. EU AI Act Art. 52 requires "
|
|
75
|
+
"transparency: users must be informed AI generates content."
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# Transparency
|
|
79
|
+
transparency = True # We always disclose AI usage
|
|
80
|
+
findings.append("Transparency requirement MET: system identifies as AI-assisted.")
|
|
81
|
+
|
|
82
|
+
# Human oversight
|
|
83
|
+
human_oversight = True # User controls all memory operations
|
|
84
|
+
findings.append("Human oversight MET: user controls store/recall/delete.")
|
|
85
|
+
|
|
86
|
+
# Risk classification
|
|
87
|
+
if not uses_gen_ai:
|
|
88
|
+
risk = "minimal"
|
|
89
|
+
findings.append("Minimal risk: no generative AI, local processing only.")
|
|
90
|
+
elif local_gen_ai:
|
|
91
|
+
risk = "minimal"
|
|
92
|
+
findings.append("Minimal risk: generative AI is local-only (Ollama).")
|
|
93
|
+
else:
|
|
94
|
+
risk = "limited"
|
|
95
|
+
findings.append(
|
|
96
|
+
"Limited risk: cloud generative AI requires transparency "
|
|
97
|
+
"disclosure and DPA with cloud provider."
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
compliant = caps.eu_ai_act_compliant
|
|
101
|
+
if not compliant:
|
|
102
|
+
findings.append(
|
|
103
|
+
"Mode C is NOT EU AI Act compliant by design. "
|
|
104
|
+
"Use Mode A or B for EU-compliant deployments."
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
return ComplianceReport(
|
|
108
|
+
mode=mode,
|
|
109
|
+
compliant=compliant,
|
|
110
|
+
risk_category=risk,
|
|
111
|
+
data_stays_local=data_local,
|
|
112
|
+
uses_generative_ai=uses_gen_ai,
|
|
113
|
+
transparency_met=transparency,
|
|
114
|
+
human_oversight=human_oversight,
|
|
115
|
+
findings=findings,
|
|
116
|
+
timestamp=datetime.now(UTC).isoformat(),
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
def verify_all_modes(self) -> dict[str, ComplianceReport]:
|
|
120
|
+
"""Generate compliance reports for all three modes."""
|
|
121
|
+
return {
|
|
122
|
+
mode.value: self.check_compliance(mode)
|
|
123
|
+
for mode in Mode
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
def get_compliant_modes(self) -> list[Mode]:
|
|
127
|
+
"""Return list of EU AI Act compliant modes."""
|
|
128
|
+
return [
|
|
129
|
+
mode for mode in Mode
|
|
130
|
+
if get_capabilities(mode).eu_ai_act_compliant
|
|
131
|
+
]
|